Reentrancy Attackとは何ですか?
Reentrancy Attackは、コントラクトが呼び出し中に別のコントラクトへ制御を渡し、その外部コードが元のコントラクトに戻ってきて記録の更新を終える前に処理を再度実行してしまう攻撃です。そのタイミングのずれにより、攻撃者は出金などの操作を何度も繰り返せます。例えば、返金を頼んで店員が金庫を閉める前にカウンターへ戻ってしまう状況を想像してください。
「古いコードだけがReentrancy Attackの被害に遭う」。それは違います。自身の状態を確定する前に外部呼び出しを行うコントラクトは、ロジックが甘ければ誰でも脆弱になり得ます。
Reentrancy Attackの仕組み
簡単な例。典型的な スマートコントラクト には、呼び出し元に資金を送るwithdraw関数があります。先に資金を送って後で残高をクリアする実装だと、攻撃者はコールバックを挟んで残高がゼロになる前にさらに要求できます。
- 開始: 攻撃者が一見正当な振る舞いを装って資金を入金します。
- 呼び出し: 攻撃者が対象コントラクトのwithdrawを実行します。
- フォールバック: 対象が資金を送ると、攻撃者側のフォールバック関数が実行されます。
- 繰り返し: そのフォールバックが残高更新の前に再びwithdrawを呼び出します。
- 枯渇: ループはコントラクトの資金かガスが尽きるまで続きます。これが手口です。
たった一つの順序のミスが大きな問題を招きます。
Reentrancy Attackが問題となる理由
タイミングのバグは実際の資金に影響を与えるため無視できません。また、この手口は開発者や興味を持つユーザーが見ただけで分かる定番の攻撃の一つです。
- 利点: パターンを知っていれば危険なコードを見つけやすく、資金を守れます。
- 視点: すべてが ブロックチェーン 上で公開され呼び出し可能なため、この手口はそこで機能しやすいです。
- 関連性: DeFi、ブリッジ、トレジャリー、さらにはガバナンス支払いを行う DAO などで見られます。
チェック、エフェクト、インタラクションの順を守ってください。まず残高を更新してから外部呼び出しを行い、追加の安全対策として簡単な再入防止ガードを入れてください。
Reentrancy Attackの主な特徴
以下が特徴です:
- 再帰: 外部コードが処理完了前に同じコントラクトへ戻って呼び出します。
- 順序: 資金送信や外部呼び出しが状態更新の前に行われると発生します。
- 波及: 一つの関数だけでなく複数のコントラクト間で影響が広がる場合があります。
- 資産: ETHやトークン、会計クレジットなど、実装がまずければ影響を受けます。
バリエーション
いくつかの型があり、いずれも不十分な実装で問題を引き起こします:
- シングル: 同じ関数に何度も入り直す。
- クロス: 同じコントラクト内の別の関数経由で入り直す。
- マルチ: 2つ以上のコントラクトにまたがってループする。
- リードオンリー: ビュー関数や価格オラクルを操作して後続の書き込みを騙す。
Reentrancy Attackの修正は一つの関数だけを直せば良いという話ではありません。すべての外部呼び出しを見直し、奇妙な呼び出し連鎖に対するテストを追加し、定期的に 監査 を行ってください。
例
2016年のThe DAOのエクスプロイトは、残高がクリアされる前のwithdrawで再入ループを利用し、数分で巨大なトレジャリーを枯渇させました。
豆知識
「チェック、エフェクト、インタラクション」の合言葉は初期のセキュリティガイドから生まれ、短く覚えやすく、実際に有効なので定着しました。
まとめ
手短に言うと:外部コードがあなたの帳簿付けを終える前に呼び出せるなら、そうするものと考え、無料の資金を渡してしまう可能性があります。それがReentrancy Attackです。
