私は無数の時間をかけて契約のエクスプロイトをデバッグしてきましたが、言わせてください - 再入攻撃はスマートコントラクトの静かな殺人者です。それらは見かけは単純ですが、壊滅的に効果的です。それが何であるか、そしてあなたの資金を drain する前にそれを止める方法について掘り下げていきましょう。再入可能性の脆弱性に初めて直面したとき、私は一見無害なコードの一部をどのように悪用できるかを理解しようとするあまり、何時間も眠れない夜を過ごしました。この概念は苛立たしいほど単純です:1つの契約が最初の実行が完了する前に別の契約にコールバックすることができます。次のように想像してみてください: ContractAは10 ETHを持ち、ContractBはその中に1 ETHを預けています。ContractBが資金を引き出すとき、ContractAはBの残高をゼロに更新する前にETHを送信します。この小さな順序の誤りが大きなセキュリティホールを生み出します。何が起こるのですか?悪意のあるコントラクトのフォールバック関数は、すぐにwithdraw()を再び呼び出します。そして、残高がまだ更新されていないため、再び残高チェックを通過します!このサイクルはContractAが完全に枯渇するまで繰り返されます。すごく賢いですね?攻撃者がこれをコードでどのように悪用するかは次のとおりです:アタックコントラクトコールwithdraw()ContractAはETHを送信し、ETHがトリガーされますfallback()fallback()は、残高が更新される前に再度withdraw()を呼び出します// 全ての資金が盗まれるまで、すすぎと繰り返しを行うこの単純な攻撃ベクターを理解していなかったために、数百万ドルを失ったプロジェクトを見てきました。まだ多くの開発者がこの初心者のミスを犯していることに腹が立ちます。## 契約を保護するための3つのテクニック1. **非リエントラント修飾子** これにより、実行中に契約がロックされ、この修飾子でマークされた関数が再入されるのを防ぎます。シンプルでありながら効果的です。2. **チェック効果相互作用パターン** これは私のお気に入りです。代わりに: 傷つきやすいrequire(balance > 0);send(ether);バランス= 0; これを行ってください: 金庫require(balance > 0); balance = 0; // 外部インタラクションの前に状態を更新するsend(ether); ETHやトークンを送信する前に、必ず状態を更新してください!3. **GlobalReentrancyGuard** 複数の相互作用する契約を持つプロジェクトに対して、これは共有ロッキングメカニズムを使用することによって、契約エコシステム全体にわたって保護を提供します。これらの技術は単なる学術的なものではなく、無数のプロジェクトを完全な財政破綻から救ってきました。多くの開発者はまだ「自分には関係ない」と思って、これらの保護策を省略しています。そんな人にならないでください。ETHやトークンを送信するすべての関数は、少なくともこれらの保護メカニズムのうちの1つを実装するべきです。スマートコントラクトの開発において、パラノイアは機能であり、バグではないことを忘れないでください。保護を一つ見逃すだけで、すべてを失う可能性があります。
再入攻撃: このスマートコントラクトの脆弱性を理解し、防ぐ方法
私は無数の時間をかけて契約のエクスプロイトをデバッグしてきましたが、言わせてください - 再入攻撃はスマートコントラクトの静かな殺人者です。それらは見かけは単純ですが、壊滅的に効果的です。それが何であるか、そしてあなたの資金を drain する前にそれを止める方法について掘り下げていきましょう。
再入可能性の脆弱性に初めて直面したとき、私は一見無害なコードの一部をどのように悪用できるかを理解しようとするあまり、何時間も眠れない夜を過ごしました。この概念は苛立たしいほど単純です:1つの契約が最初の実行が完了する前に別の契約にコールバックすることができます。
次のように想像してみてください: ContractAは10 ETHを持ち、ContractBはその中に1 ETHを預けています。ContractBが資金を引き出すとき、ContractAはBの残高をゼロに更新する前にETHを送信します。この小さな順序の誤りが大きなセキュリティホールを生み出します。
何が起こるのですか?悪意のあるコントラクトのフォールバック関数は、すぐにwithdraw()を再び呼び出します。そして、残高がまだ更新されていないため、再び残高チェックを通過します!このサイクルはContractAが完全に枯渇するまで繰り返されます。すごく賢いですね?
攻撃者がこれをコードでどのように悪用するかは次のとおりです:
アタックコントラクトコールwithdraw() ContractAはETHを送信し、ETHがトリガーされますfallback() fallback()は、残高が更新される前に再度withdraw()を呼び出します // 全ての資金が盗まれるまで、すすぎと繰り返しを行う
この単純な攻撃ベクターを理解していなかったために、数百万ドルを失ったプロジェクトを見てきました。まだ多くの開発者がこの初心者のミスを犯していることに腹が立ちます。
契約を保護するための3つのテクニック
非リエントラント修飾子
これにより、実行中に契約がロックされ、この修飾子でマークされた関数が再入されるのを防ぎます。シンプルでありながら効果的です。
チェック効果相互作用パターン
これは私のお気に入りです。代わりに:
傷つきやすい require(balance > 0); send(ether); バランス= 0;
これを行ってください:
金庫 require(balance > 0); balance = 0; // 外部インタラクションの前に状態を更新する send(ether);
ETHやトークンを送信する前に、必ず状態を更新してください!
GlobalReentrancyGuard
複数の相互作用する契約を持つプロジェクトに対して、これは共有ロッキングメカニズムを使用することによって、契約エコシステム全体にわたって保護を提供します。
これらの技術は単なる学術的なものではなく、無数のプロジェクトを完全な財政破綻から救ってきました。
多くの開発者はまだ「自分には関係ない」と思って、これらの保護策を省略しています。そんな人にならないでください。ETHやトークンを送信するすべての関数は、少なくともこれらの保護メカニズムのうちの1つを実装するべきです。
スマートコントラクトの開発において、パラノイアは機能であり、バグではないことを忘れないでください。保護を一つ見逃すだけで、すべてを失う可能性があります。