Develop with pleasure!

福岡でCloudとかBlockchainとか。

DoS攻撃を可能にする脆弱性CVE-2018-17145とBitcoin Coreの脆弱性の内容と対応

最近公開された、2018年のBitcoin Coreに存在していた脆弱性に関するペーパー↓

https://invdos.net/paper/CVE-2018-17145.pdf

この脆弱性はもともとJavaScriptで実装されたBitcoinのノード実装の1つであるBcoinで発見されたもので、攻撃方法は同じでも、BcoinとBitcoin Coreで脆弱性が現れるコードには違いがある。またホワイトペーパーの説明にも一部間違いがあるようなので、その辺りも含めてみてみよう。

尚、Bitcoin Coreの場合、脆弱性の影響を受けるのはv0.16.0とv0.16.1とされている。

CVE-2018-17145の内容

CVE-2018-17145はBitcoinP2Pメッセージの1つであるinvメッセージを使った攻撃になる。

invメッセージ

invメッセージは新しいブロックやトランザクションの存在を接続中のピアに通知する際に使用される。invメッセージにはブロックやトランザクションのハッシュのリストが含まれており、invを受信したノードは自身が対象のハッシュ値を持つブロックやトランザクションを持っていない場合、それをgetdataメッセージを使って要求する。

invメッセージは最大50,000個のアイテム(トランザクションハッシュ or ブロックハッシュ)を一度に送信できる。

攻撃方法

攻撃方法はシンプルで、インバウンド接続を許可しているノードに接続し、49,999個(ペーパーに49,999と書いてあるけど、上限が50,000なので別に50,000でも良いはず)のランダムなハッシュを生成し、それをinvメッセージにセットして送信し、getdata要求が来ても対応するトランザクションデータを返さない(元々ランダムに生成したハッシュなので、その元となる有効なトランザクションなど存在しない)。これを繰り返す。

ただ、この攻撃が成功するには、攻撃対象のノードがinvメッセージで受信したハッシュリストの内、未確認のハッシュをトラッキングしているノードである必要がある。未確認のトランザクションのハッシュをメモリ上に保持しているノード実装であれば、この攻撃の影響を受ける。1 Gbpsのネットワーク帯域を持つ環境であれば、125MB/秒のデータ送信が可能なため、49,999個の32バイトのハッシュ値を持つinvメッセージを秒間約83回送信できる。するとすぐにノードのメモリは膨れ上がり、Out-of-Memoryを引き起こさせノードを応答不能にすることができる。

ノードは対応するTxが確認されていない状態で受信したinvのTxのリストを他のピアにリレーすることはないので、この攻撃はターゲットノードを絞った上で個別に実行する必要がある。

Bcoinの脆弱性の原因

Bcoinでは、↑のように受信したinvのアイテムをトラッキングしていたため、この攻撃の影響を受ける。

このinvのアイテムをトラッキングしている理由は、未確認のトランザクションハッシュのトランザクションについてgetdataメッセージでデータを要求する際、既にgetdataを送信済みのデータについて再度getdataを送信しないために送信済みかどうかのチェックをするためで、BcoinではハッシュリストをMapで管理していた。

修正のコミットは↓で、

github.com

1つのピアからの未確認トランザクションハッシュの上限を10000とし、それを超えたらそのピアとの接続を切断するという対策を採っている。

Bitcoin Coreの脆弱性の原因

Bitcoin Coreの場合、Bcoinのように単純にトラッキングしているという訳ではなく、正確にはinvで受信したアイテムをウォレットに通知するためのコードが対象になる。

↑のペーパーで、Bitcoin Coreでもinvメッセージを処理する際の機能していない制限のチェックとして以下のsrc/net_processing.cppのコードをリストアップしているが、これは正しくない。このコードはinvメッセージ内のハッシュの数が50,000より大きくないかチェックしてるだけであって、invのトラッキングをするためのコードではない。

if (vInv.size() > MAX_INV_SZ)
{
  LOCK(cs_main);
  Misbehaving(pfrom->GetId(), 20,
  strprintf("message inv size() = %u",
  vInv.size()));
  return false;
}

実際にBitcoin Coreでチェックが動作しているのは、src/net.cppの以下のコードだ

if (mapAskFor.size() > MAPASKFOR_MAX_SZ || setAskFor.size() > SETASKFOR_MAX_SZ)
   return;

mapAskForは、未確認のトランザクションハッシュについて、getdataを要求するために確保されるMapで、MAPASKFOR_MAX_SZinvの上限となじ50,000なので、Bcoinと同じトラッキングの意味としては、対応がされている。

Bitcoin Coreで実際に問題となるのは、トラッキングではなくinvで受信したアイテムをウォレットに通知するためのコードになる。そのコードが、src/net_processing.cppinvメッセージを処理の最後の以下のコード↓

// Track requests for our stuff
GetMainSignals().Inventory(inv.hash);

ペーパーでは、その中身(src/validationinterface.cpp)がリストアップされている↓

void CMainSignals::Inventory(const uint256 &hash) {
    m_internals->m_schedulerClient.AddToProcessQueue([hash, this] {
        m_internals->Inventory(hash);
    });
}

このコードが導入されたのが↓のコミットで、

github.com

ウォレットへのinvのアイテムの通知処理を、バックグラウンドスレッドで動作するようキュー化したもの。この場合、攻撃はバックグラウンドで動作する通知処理のスピードを、invで送られてくるアイテムによるキューの成長が上回る場合に成功する。実際にどういうマシンスペック、ネットワーク帯域の下で成功するかについては、シミュレーション結果などはホワイトペーパーでは提供されていない。

問題のコードは以下のコミットで修正されている。

github.com

修正方法としては、Inventoryの通知自体を削除している。これはバックグラウンドのキュー化以前に戻してもmainスレッドでの処理は発生するからか?まぁ未確認のハッシュをそもそもウォレットに通知する必要もないのだろうけど。

Bitcoin Core以外にもLitecoinやbtcdなどにも影響があったようだが、↑でも分かるよう実装によって脆弱性のポイントは違う可能性はある。