まず、再入攻撃とは何か、そしてそれを防ぐにはどうすればよいかをわかりやすく説明します。次に、脆弱性がどこにあるのか、攻撃者のコードは何かを示すためにコード例を詳しく見ていきます。そして最も重要なこととして、プロジェクト内の 1 つのスマート コントラクトだけでなくすべてのスマート コントラクトを保護するための最新の検証済み方法を紹介します。

ネタバレ: nonReentrant() 修飾子についてすでに聞いたことがある場合は、読み続けてください。globalNonReentrant() 修飾子と checks-effects-interactions パターンの数行下に記載されています。



上の画像には、ContractA と ContractB があります。ご存知のとおり、スマート コントラクトは別のスマート コントラクトと対話できます。この場合、ContractA は ContractB を呼び出すことができます。つまり、再入可能性の基本的な考え方は、ContractA がまだ実行されている間に、ContractB が ContractA にコールバックできるということです。

それで、攻撃者はこれをどのように利用できるのでしょうか?

上記では、ContractA には 10 個の Ether があり、ContractB は ContractA に 1 個の Ether を保管していることがわかります。この場合、ContractB は ContractA の withdraw 関数を使用して、残高が 0 より大きいチェックに合格すると Ether を自身に送り返し、合計残高を 0 に変更することができます。



次に、ContractB が再入可能性を利用して withdraw 関数を悪用し、ContractA からすべての Ether を盗む方法を見てみましょう。基本的に、攻撃者は attack() と fallback() という 2 つの関数を必要とします。

Solidity では、フォールバック関数は名前、パラメーター、戻り値を持たない外部関数です。コントラクト内に存在しない関数を呼び出す、必要なデータを渡さずに関数を呼び出す、データなしで Ether をコントラクトに送信するなど、誰でもフォールバック関数を呼び出すことができます。

再入可能性の仕組みは(矢印をステップごとに追ってみましょう)、攻撃者が attack() 関数を呼び出し、その関数が内部で ContractA から withdraw() 関数を呼び出すというものです。関数内部では、ContractB の残高が 0 より大きいかどうかを確認し、大きい場合は実行を続行します。



ContractB の残高は 0 より大きいため、1 Ether が送り返され、フォールバック機能が起動します。この時点で ContractA には 9 Ether があり、ContractB にはすでに 1 Ether があることに注意してください。



次に、フォールバック関数が実行されると、ContractA の引き出し関数が再度トリガーされ、ContractB の残高が 0 より大きいかどうかが再度確認されます。上の画像を再度確認すると、残高がまだ 1 Ether であることがわかります。



つまり、チェックに合格し、ContractB に別の Ether が送信され、フォールバック関数がトリガーされます。「balance=0」の行は実行されないため、ContractA のすべての Ether がなくなるまでこれが続くことに注意してください。

___________

ここで、Solidity コードを使用して再入可能性を識別できるスマート コントラクトを見てみましょう。



EtherStore コントラクトには、送信者の残高を保存および更新する関数 deposit() と、保存されているすべての残高を一度に取得する関数 withdrawAll() があります。withdrawAll() の実装では、最初に残高が 0 より大きいことを require でチェックし、その直後に Ether を送信し、最後に送信者の残高を 0 に更新することに注意してください。



ここでは、再入可能性を利用して EtherStore コントラクトをドレインするコントラクト Attack があります。そのコードを分析してみましょう。

  • 攻撃者はコンストラクタ内で EtherStore アドレスを渡してインスタンスを作成し、その関数を使用できるようにします。

  • ここで、EtherStore がこの契約に Ether を送信するときに呼び出される fallback() 関数を確認できます。内部では、残高が 1 以上である限り、EtherStore から withdraw を呼び出します。

  • attack() 関数内には、EtherStore を悪用するロジックがあります。ご覧のとおり、まず十分な Ether があることを確認し、攻撃を開始します。次に、EtherStore の残高が 0 より大きくなるように 1 Ether を入金し、引き出しを開始する前にチェックに合格します。

上記の ContractA と ContractB の例で、コードがどのように実行されるかを段階的に説明しました。それでは、それがどのように実行されるかをまとめてみましょう。まず、攻撃者は attack() を呼び出します。この内部で EtherStore から withdrawAll() が呼び出され、次に Ether が Attack コントラクトのフォールバック関数に送信されます。そして、そこで再入が開始され、EtherStore の残高が枯渇します。

では、再入攻撃からコントラクトを保護するにはどうすればよいでしょうか?

これらを完全に保護するための 3 つの防止手法を紹介します。単一関数内の再入、関数間の再入、コントラクト間の再入を防止する方法について説明します。



単一の関数を保護する最初の手法は、noReentrant と呼ばれる修飾子を使用することです。

修飾子は、他の関数の動作を変更するために使用する特別なタイプの関数です。修飾子を使用すると、関数全体を書き直さなくても、関数に追加の条件や機能を追加できます。

ここで行うことは、関数の実行中にコントラクトをロックすることです。この方法では、関数のコードを調べて、ロックされた状態変数を false に変更し、require で行われたチェックに再度合格する必要があるため、単一の関数に再び入ることができなくなります。

___________



2 番目の手法は、Checks-Effects-Interactions パターンを利用して、コントラクトを関数間の再入から保護することです。上記の更新された EtherStore コントラクトで何が変更されたかわかりますか?

Check-Effects-Interaction パターンを詳しく知るには、https://fravoll.github.io/solidity-patterns/checks_effects_interactions.html を読むことをお勧めします。





上記では、左側の画像の脆弱なコードと、残高が Ether の送信後に更新されたコード (上記のように潜在的に決して到達できない可能性があります) との比較を示しています。右側では、require(bal > 0) (チェック) の直後、Ether の送信 (対話) の前に、balances[msg.sender] = 0 (または効果) を移動しています。

この方法により、別の関数がwithdrawAll()にアクセスしている場合でも、Etherを送信する前に残高が常に更新されるため、このコントラクトは攻撃者から保護されます。

パターンは https://twitter.com/GMX_IO によって作成されました

3 番目に紹介する手法は、コントラクト間の再入を防ぐために GlobalReentrancyGuard コントラクトを作成することです。これは、複数のコントラクトが相互に作用するプロジェクトに適用できることを理解することが重要です。

ここでの考え方は、最初のテクニックで説明した noReentrant 修飾子と同じです。修飾子に入り、変数を更新してコントラクトをロックし、コードが終了するまでロックを解除しません。ここでの大きな違いは、関数に入ったかどうかを確認する場所として使用される別のコントラクトに格納された変数を使用していることです。







ここでは、実際のコードを使わず、関数名だけを使ってアイデアを理解するための参考例を作成しました。私の経験から、言葉で書くよりも状況を視覚化するのに役立つと思います。

ここで、攻撃者は ScheduledTransfer コントラクト内の関数を呼び出し、条件を満たした後に指定された Ether を AttackTransfer コントラクトに送信します。これにより、フォールバック関数が実行され、ScheduledTransfer コントラクトの観点からトランザクションが「キャンセル」され、Ether が受信されます。このようにして、ScheduledTransfer からすべての Ether が排出されるまで監視が開始されます。

さて、上で述べた GlobalReentrancyGuard を使用すると、このような攻撃シナリオを回避できます。

__________________

Twitter @TheBlockChainer では、スマート コントラクト、Web3 セキュリティ、Solidity、スマート コントラクトの監査などに関する毎日の最新情報をご覧いただけます。

__________________