Ethereum最凶の脆弱性をコントラクト実行の仕組みから読み解く&検査ツール紹介
こんにちは、Y.C.です。Ethereumスマートコントラクトには一般的なwebアプリケーションとは異なる脆弱性があるのをご存知ですか?特にリエントランシーはEthereumの実行の仕組みに起因していて特異性が高く、その被害規模の大きさと合わせて最も悪名高い脆弱性となっています。今回はリエントランシーの原理をブロックチェーン上でどのようにコントラクトが実行されるかという観点で解き明かし、さらに検査ツールについても紹介したいと思います。
TL;DR
- Ethereumではトランザクションにより送金やコントラクトの実行・生成を行う
- リエントランシーはトランザクションの仕組みを利用した凶悪な脆弱性
- 検査ツールを使って脆弱性がないか事前にチェックしよう
はじめに
スマートコントラクトのリスク
情報セキュリティの世界ではリスク = 被害の大きさ × 発生確率として表しますが、スマートコントラクトはこの両方とも大きいです。つまりリスクは特大です。- 被害の大きさ
スマートコントラクトでは金銭的な価値を扱うことが多いです。そもそも何をするにしても手数料がかかるため、お金が絶対に関わってきます。脆弱性が金銭的なダメージに直結します。 - 発生確率
一度ブロックチェーン上にデプロイしたコントラクトは後から修正することができず、全世界に公開されます。万が一脆弱性があった場合、長期に渡って攻撃される可能性が極めて高いです。
リエントランシーとは?
リエントランシーとは、不正に再帰的な関数実行を引き起こす脆弱性です。2016年に発生したThe DAO事件ではリエントランシーが原因で、当時で約52億円もの通貨が盗まれる被害が発生しました。またチェーンが分岐してEthereum Classicという新たなブロックチェーンが生まれる契機となりました。コントラクト実行原理
登場人物
Ethereumの世界で主体となるのがアカウントです。送金するのもアカウント、コントラクトを実行するのもアカウント、そしてコントラクトもアカウントです。もう少し詳しく言うと以下の2種類があります。- EOA(外部所有アカウント):一般的に想像するところのアカウントがこれです。アドレスで識別し、送金したりされたりします。
- コントラクトアカウント:コントラクトの実体で、EOAと同じようにアドレスや通貨残高を持ちます。加えて、EVMバイトコード(スマートコントラクトの実行コード)やデータの格納領域を持ちます。オブジェクト指向言語のインスタンスがメソッドやインスタンス変数を持つようなイメージです。

できること
Ethereumでアカウントが出来ることを挙げてみると、大きく以下の3点があります。- 送金
アカウントは送金ができます。ウォレットアプリを使うと自分のアカウントで送金できますね。 - コントラクト実行
アカウントはコントラクトアカウントを実行することができます。最近だとNFTを発行するのが流行っていますね。 - コントラクト生成
アカウントは新しいコントラクトアカウントを生成することができます。生成されて初めてコントラクトアカウントはブロックチェーン上に存在できます。

実はこの3つは全部トランザクションによって実現されます。トランザクションは何かというと、宛先、送金額、送信データ、手数料などを指定して、あるアカウントが別のアカウントに何かしら作用をもたらすもの、です。このトランザクションにリエントランシーを誘発する注意点があるので、詳しくみていきます。
- 送金
toに送金するアカウントのアドレス、valueに送金額、dataに適当なデータ、gasに手数料を指定します。
- コントラクト実行
toに実行するコントラクトアカウントのアドレス、valueに送金額、dataに実行に必要なデータ、gasに手数料を指定します。
- コントラクト生成
toに0(コントラクト生成用の特別なアドレス)、valueに送金額、dataに生成するコントラクトアカウントのバイトコード、gasに手数料を指定します。実行コードに含まれるコンストラクタ(初期化コード)がその場で実行されます。
注意点
コントラクトアカウントに対しては、送金と実行が同じ構造となっています。送金しただけのつもりが、意図せず送金先のコードを実行してしまう可能性があります。
実行処理の分岐
コントラクトアカウントに対してトランザクションを発行するとバイトコードが実行されることが分かりました。しかし、通常コントラクトを高級言語で記述する際、処理内容は関数として表現します。トランザクションが発行されると、どのように関数が実行されるのでしょうか?実はトランザクションという観点では関数は存在しません。代わりに関数名と引数の型を元に得られた関数IDというものが送信データの先頭4byteに含まれていて、バイトコードの中で関数IDで条件分岐することで実行する処理が選ばれます。

注意点
送信データに含まれる関数IDがバイトコードの中のどの分岐条件にも合致しなかった場合(if – else を繰り返した最後のelseの部分)でも実行は継続します。つまり、送金や実行を行なった際、宛先のコントラクトのelse部分に記述されたコードを意図せず実行してしまう可能性があります。
リエントランシー動作原理
前述した注意点を踏まえ、リエントランシーがどのように発動するのかみてみます。被害者コード[1]

このコントラクトは銀行のような役割を想定していて、donate()を実行すると預金、withdraw()を実行すると出金ができます。creditというマッピングでどのアカウントがいくらの残高を持っているかを記録しています。
このコードでポイントとなるのが、29行目の送金処理の後に30行目の残高を減らす処理を行っている点です。通常であれば残高がなければ送金処理は行われませんが、この順番が攻撃の鍵となります。
攻撃者コード

攻撃コードの一例です。ここで重要なのが12行目の関数です。あれ、名前がないですね?これはfallback関数と呼ばれるものです。
fallback関数とは:以下のような場合に実行される特殊な関数です。
- このコントラクトアカウントに対する送金時
- このコントラクトアカウントを実行する際に指定した関数が存在しない(名前が違う、引数の型が違う)場合
攻撃フロー
攻撃の流れです。攻撃コントラクトは事前に被害者コントラクトのアドレスが与えられているものとします。
- 攻撃者のattack関数を実行すると、被害者側のdonate関数で適当な額を預金し、withdraw関数で同じ額を出金します。
- withdraw関数の中で送金する際、関数IDの指定なしで攻撃者にトランザクションが発行されます。すると攻撃者のfallback関数が実行されます。
- fallback関数の中で再び被害者側のwithdraw関数を呼び出金を行います。この時点でまだ被害者側が管理する攻撃者の残高が減っていないため、再び送金処理が行われます。
- 2と3が繰り返されます。
対策方法
リエントランシーの対策方法は以下です。- 状態変数を変更したあとでトランザクションを実行することを徹底する
先ほどの例では、残高を変更する前に送金を行なっていたことが問題でした。残高がなければ送金は行われませんでした。このようにトランザクションの実行条件となっている変数は、トランザクションを実行する前に変更しましょう。 - 送金時のgas量を制限する
Solidityのtransfer関数やsend関数を使うと使用するgas量(手数料)が2300という小さい値で固定されるため、再帰呼び出しのような重い処理は失敗します。ただし、今後バイトコードを実行するために必要なgas量は変更される可能性があるため、根本解決ではないという指摘もあります。(https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/)
検査ツール紹介
ここからは、リエントランシーを含むEthereumスマートコントラクトの脆弱性を検査するツールをご紹介します。実際にいくつかのツールを試してみたので、検出できる脆弱性の種類と合わせて使用感をお伝えします。検出対象の脆弱性
EthereumスマートコントラクトにはSWC(https://swcregistry.io/)という脆弱性データベースが存在しますが、どの脆弱性がどれくらいの危険度なのかを示すよく知られた指標はないようです。ここでは、こちらの論文[2]をもとに危険度の高い脆弱性(Severity: H)をピックアップし、各ツールが検出できるかを見てみたいと思います。個々の脆弱性の詳細についてはSWCを参照下さい。もちろんリエントランシーはSeverity:Hとなっています。
検査対象による分類

検査対象によってツールは2つに大別できます。1つはSolidityのコード、もう1つはEVMバイトコードです。表中でそれぞれのメリットを黄色、デメリットを青で色付けしています。バイトコードを対象にしたツールはシンボリック実行という網羅性の高い擬似実行が主流です。検査に時間はかかるものの、実際にブロックチェーン上にデプロイされるありのままのコードを検査できるのがメリットです。本当はシンボリック実行についてガッツリご紹介したいのですが、文量が多くなるのでまた次の機会に…
ツール紹介
https://consensys.github.io/smart-contract-best-practices/security_tools/こちらで紹介されているように、有名な検査ツールがあるのでいくつか試してみました。
Slither
https://github.com/crytic/slitherSolidityコードを対象にしたツールで最高に使いやすいです。
- 導入の容易さ:◎
pipでインストールするだけです。 - 実行速度:◎
一瞬でおわります。

短いサンプルコードを検査した際の出力結果です。わずか1秒たらずで検査でき、リエントランシーも検出できました。ソースコードのどの部分に脆弱性があるか教えてくれるのはSolidityを対象にしているツールならではです。

Slitherで検出できる危険度の高い脆弱性です。多くの脆弱性を検出できる一方、実行フローに応じて発生する脆弱性は検出確度がmedium以下となっています。
Mythril
https://github.com/ConsenSys/mythrilSolidityコードを元に、コンパイルをして得られたバイトコードに対してシンボリック実行で検査を行うツールです。
- 導入の容易さ:×(記事執筆時点)
- Mac OSX: C++の名前空間のエラー
- Docker for Mac, Ubuntu (on Vagrant on Mac):
エラーがでてしまいます。内部でコンパイルをする際に出ているようなのでツールの導入自体はできているようです(もちろんこのコードを直接コンパイルすると成功します)。バージョンを変えたり環境を変えたりすると正しく検査できるかもしれませんが、コマンド一発で入るSlitherと比べると手間ですね。
- 実行速度: 未検証
今回実際に試すことはできませんでしたが、シンボリック実行を行うため時間はかなりかかると思います。(数十秒〜数十分スパン?)

Mythrilで検出できるとされている脆弱性です。Slitherで検出確度がmediumだったり、そもそも検出できない整数演算系の脆弱性が検出できるのが興味深いですが、どの程度の精度があるのかは不明です。
Manticore
https://github.com/trailofbits/manticoreシンボリック実行ツールです。
- 導入の容易さ:○
pipでインストールするだけです。ちょっと動かすコマンドはわかりづらいです。基本`manticore {solidityファイル}`でいいとは思います。今もう一度動かそうとすると動かないですね、なぜだろう… - 実行速度:×
シンボリック実行ツールなので仕方ない面もあると思いますが、小さいサンプルでも非常に時間がかかりました。 - 検出脆弱性:×
簡単なリエントランシーのサンプルも検出できなかったです。脆弱性検証というよりは、プログラムの実行フローの探索自体が主目的のようです。コマンドあってるかな…
RA
https://github.com/wanidon/RAリエントランシーの検出に特化したシンボリック実行ツールです。他のツールがパターン検出であるのに対し、このツールはリエントランシーの攻撃フローそのものをシンボリック実行で再現し、攻撃成功の可否によって脆弱かを検出します。ちなみに作者は日本人です。一人で作っています。偉いですね…!
- 導入の容易さ:◎
pipでインストールするだけです。 - 実行速度:△×
Manticoreほどではないですが時間はかかります。 - 検出脆弱性:リエントランシーのみ
コントラクトアカウント生成時のコンストラクによるリエントランシー等、一般的なパターン検出では検知が難しいとされる高度なリエントランシーにも対応することを目指しています。
Oyente
https://github.com/enzymefinance/oyenteシンボリック実行ツールの先駆けです。偉大な先人ですが、python2で記述されており、もはやまともに導入できません。更新も止まっています。
MythX
https://mythx.io/Mythrilのオンライン版です。有料ですが、検査結果を綺麗にレポートとして出力してくれるようです。結果を開発者以外にも共有する必要がある場合はとても有用だと思います。
おわりに
リエントランシーの仕組みや最近の検査ツールについてご紹介しました。実際のブロックチェーン上で何が起こっているのかを大まかにでも把握しておくと、より安全なスマートコントラクトを記述する一助となるのではと思います。検査ツールを使うと開発者の負担は大きく軽減されますが、もちろん精度が保証されているわけではないです。人の目による精査に加え、必要に応じて専門機関に監査を依頼することも検討してください。とはいえ、スマートコントラクトの人気は衰えるところを知らず、検査ツールも日々更新されています。今後もどんどん便利になっていくことに期待です!次世代システム研究室では、 Web アプリケーション開発を行うアーキテクトを募集しています。募集職種一覧 からご応募をお待ちしています。
参考文献
[1]Re-Entrancy Attack Patterns https://github.com/uni-due-syssec/eth-reentrancy-attack-patterns/blob/master/simple.sol
[2]Agarwal, Rachit, Tanmay Thapliyal and Sandeep K. Shukla. “Vulnerability and Transaction behavior based detection of Malicious Smart Contracts.” ArXiv abs/2106.13422 (2021): n. pag.