スマートコントラクトの開発は言わずもがな、バグが命取りです。億単位のハッキング事例がたくさんありますよね。
本講座では、よくある?脆弱性をご紹介します。
今回取り上げるのはリエントランシー攻撃。実例としてかの有名な「The DAO事件」が挙げられます。
前提知識
コントラクトは便利なことに他のコントラクトを呼び出すことが可能です。
コントラクトアドレスはイーサの送金時に実行されるfallback関数を設定することができます。fallback関数が呼び出されるのは以下の2通り
- 呼び出しに一致する関数が存在しないとき
- コントラクトアドレスへのイーサ送金時にreceive関数が設定されていない時
fallback関数についてより詳しく知りたい方はこちらhttps://docs.soliditylang.org/en/v0.8.12/contracts.html?highlight=fallback#fallback-function
リエントランシー攻撃とは?
リエントランシーという単語は再入場(Re-entry)から来ているらしいです。その名の通り、コントラクトAが悪いコントラクトBを呼び出すと、悪いコントラクトBはコントラクトAを再度呼び出します。
それが、なぜ最悪な結果をもたらす可能性があるのか
例えばコントラクトAはたくさんのetherを持っていて、いろんな人に配りたい。そこで、以下のような動作を行う関数fがあるとします。
- bool型変数 alreadyGivenが、falseであることを確認。trueなら終了
- msg.senderに1etherを送金
- alreadyGivenをtrueに変える。
一度この関数fを実行すると、alreadyGivenがtrueになるので、1etherしかもらえないはずです。
悪いコントラクトBを使った攻撃方法を示します。
まず、悪いコントラクトBからコントラクトAの関数fを実行する。2の処理により1etherが送金されてくると、fallback関数が動き出します。fallback関数から関数fを呼び出すとする。すると、もう一度fの1から処理が始まるのです!3の処理はまだ実行されていないため、alreadyGivenはfalseのままなので、もちろんまた1etherが送金されます。
fallback関数内からコントラクトAの残高をみていれば、そこで実行を終了してあげることで、金が尽きて送金処理に失敗してリバートするみたいなことも回避可能です。
リエントランシー攻撃のコード例 → https://solidity-by-example.org/hacks/re-entrancy/
対策
- 送金や外部コントラクト呼び出しは、ステートを変化させてから行う
今回の関数fは2と3の順を入れ替えればおkです。 - ReEntrancyGuardを使う https://solidity-by-example.org/hacks/re-entrancy/
modifier noReentrantをfにつけると、再実行を防ぐlockをかけることが可能。
openzeppelinの実装: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/security/ReentrancyGuard.sol
この講座は次回があるかはわかりません。やる気次第です。間違いがあったら指摘お願いします。