スマートコントラクトのセキュリティに関する問題の中で、多くの開発者が見落としがちなものの一つにリエントランシー攻撃があります。もしあなたがSolidityでスマートコントラクトを構築しているなら、これは絶対に理解しておくべき重要なポイントです。



最もシンプルに言えば、リエントランシーは、あるコントラクトが別のコントラクトを呼び出し、その呼び出されたコントラクトが再び最初のコントラクトを呼び出すことができる状態のことを指します。例えば、ContractAに10 Etherがあり、ContractBがそこに1 Etherを送ったとします。ContractBが引き出しを行う際、ContractAは残高が0より大きいかどうかを確認し、もしそうならEtherを送ります。しかし、もしContractBに(fallback関数)(予備関数)が定義されている場合、その関数が呼び出されると、ContractAの引き出し関数が完了する前に再び呼び出される可能性があります。結果として、ContractBの残高は1 Etherのままで、もう一度Etherを受け取り続け、最終的にContractAの資金が枯渇するまで繰り返されるのです。

この攻撃はどのように動作するのでしょうか?攻撃者は二つのものを用意します:攻撃開始用の(attack)関数と、再帰呼び出しを行うためのfallback関数です。fallback関数は特別な外部関数で、名前も引数もなく、存在しない関数を呼び出したり、データを送信せずにEtherを送ったりすることで誰でも呼び出すことができます。

具体例として、EtherStoreコントラクトがあります。このコントラクトには(deposit)関数で残高を記録し、(withdrawAll)関数で全額引き出す機能があります。ただし、問題は(withdrawAll)が残高を確認し、Etherを送った後に残高を0に更新している点です。これにより、リエントランシー攻撃の余地が生まれます。

では、どうやって防ぐのか?以下に三つの方法を紹介します。

一つ目は、(noReentrant)修飾子を使うことです。これは非常にシンプルなアイデアで、関数の実行中にロックをかける仕組みです。もし誰かが再び同じ関数を呼び出そうとした場合、ロック状態を確認し、解除されるまで呼び出しを拒否します。修飾子は、特定の条件を関数に付加できる特殊な関数で、全てのロジックを書き換えることなく条件付けが可能です。

二つ目は、「Check-Effect-Interaction」パターンを適用することです。条件の確認や送金を行う前に、まず状態を更新します。具体的には、送金前に残高を即座に0に設定し、その後に外部とのやり取りを行います。こうすることで、リエントランシーが発生しても、残高は既に0に更新されているため、攻撃者は追加の引き出しを行えなくなります。

三つ目は、複数のコントラクトが相互に連携している場合に有効なGlobalReentrancyGuardを導入することです。これは、システム全体を一つの状態変数でロックし、どのコントラクトの関数が呼び出されても、その状態を確認してロックされていれば処理を拒否します。これにより、複数のコントラクト間の連鎖的なリエントランシー攻撃を防止できます。

これら三つの方法は、状況に応じて組み合わせて使うことも可能です。重要な関数にはnoReentrantを使い、多くの関数を扱う場合はCheck-Effect-Interactionを適用し、複雑なシステム全体にはGlobalReentrancyGuardを導入する、といった具合です。リエントランシーの理解とその対策をしっかり行うことで、より安全なスマートコントラクトの構築が可能になります。
原文表示
このページには第三者のコンテンツが含まれている場合があり、情報提供のみを目的としております(表明・保証をするものではありません)。Gateによる見解の支持や、金融・専門的な助言とみなされるべきものではありません。詳細については免責事項をご覧ください。
  • 報酬
  • コメント
  • リポスト
  • 共有
コメント
コメントを追加
コメントを追加
コメントなし
  • ピン