ウェブサイト検索

リエントランシー攻撃とは何ですか?また、どのように機能しますか?


Web 3 企業に数百万ドルの損害を与えた最も一般的なスマート コントラクト ハッキングがどのように行われるかは次のとおりです...

ブロックチェーン業界における大規模なハッキングの一部では、数百万ドル相当の暗号通貨トークンが盗まれましたが、その原因は再入攻撃でした。これらのハッキングは近年あまり一般的ではなくなりましたが、依然としてブロックチェーン アプリケーションとユーザーにとって重大な脅威となっています。

では、リエントラント攻撃とは正確には何でしょうか?それらはどのように展開されますか?また、そのような事態を防ぐために開発者が講じられる対策はあるのでしょうか?

リエントランシー攻撃とは何ですか?

再入攻撃は、脆弱なスマート コントラクト機能が悪意のあるコントラクトへの外部呼び出しを行い、トランザクション フローの制御を一時的に放棄するときに発生します。その後、悪意のあるコントラクトは、実行が完了する前に元のスマート コントラクト関数を繰り返し呼び出し、資金を使い果たします。

基本的に、イーサリアム ブロックチェーン上の出金トランザクションは、残高確認、送金、残高更新の 3 段階のサイクルに従います。サイバー犯罪者が残高更新前にこのサイクルを乗っ取ることができれば、ウォレットが空になるまで繰り返し資金を引き出すことができます。

Coindesk が取り上げた最も悪名高いブロックチェーン ハッキングの 1 つであるイーサリアム DAO ハッキングは、6,000 万ドル以上相当のイーサの損失をもたらし、第 2 位の仮想通貨の方向性を根本的に変えたリエントラント攻撃でした。

リエントラント攻撃はどのように機能するのでしょうか?

あなたの故郷にある、善良な地元の人たちがお金を預けている銀行を想像してみてください。その総流動性は100万ドルです。しかし、この銀行には会計システムに欠陥があり、行員は銀行残高の更新を夕方まで待っています。

あなたの友人の投資家が町を訪れ、会計上の欠陥を発見します。彼はアカウントを作成し、100,000 ドルを入金しました。翌日、彼は10万ドルを引き出しました。 1 時間後、彼はもう一度 100,000 ドルを引き出しようとしました。銀行は彼の残高を更新していないため、残高はまだ 100,000 ドルと表示されています。それで彼はお金を手に入れます。彼はお金がなくなるまでこれを繰り返します。スタッフは夕方帳尻を合わせたときに初めてお金がないことに気づきます。

スマート コントラクトのコンテキストでは、プロセスは次のように進みます。

  1. サイバー犯罪者は、脆弱性のあるスマート コントラクト「X」を特定します。
  2. 攻撃者は、悪意のあるコントラクト「Y」に資金を送金するために、ターゲットのコントラクト X に対して正当なトランザクションを開始します。実行中に、Y は X の脆弱な関数を呼び出します。
  3. コントラクトが外部イベントとの対話を待機しているため、X のコントラクトの実行は一時停止または遅延されます。
  4. 実行が一時停止されている間、攻撃者は X の同じ脆弱な関数を繰り返し呼び出し、可能な限り何度もその実行をトリガーします。
  5. 再入力するたびにコントラクトの状態が操作され、攻撃者が X から Y に資金を流出できるようになります。
  6. 資金がなくなるとリエントリーが停止し、X の遅延執行が最終的に完了し、最後のリエントリーに基づいて契約の状態が更新されます。

一般に、攻撃者は再入可能の脆弱性をうまく悪用し、契約から資金を盗みます。

リエントランシー攻撃の例

では、再入攻撃は、導入時に技術的にどのように発生する可能性があるのでしょうか?これは、リエントランシーゲートウェイを備えた仮想のスマートコントラクトです。理解しやすいように、公理的な名前を使用します。

 // Vulnerable contract with a reentrancy vulnerability
pragma solidity ^0.8.0;
contract VulnerableContract {    mapping(address => uint256) private balances;
    function deposit() public payable {        balances[msg.sender] += msg.value;    }
    function withdraw(uint256 amount) public {        require(amount <= balances[msg.sender], "Insufficient balance");        (bool success, ) = msg.sender.call{value: amount}("");        require(success, "Transfer failed");        balances[msg.sender] -= amount;    }} 

VulnerableContract を使用すると、 ユーザーはデポジット 関数を使用して eth をコントラクトにデポジットできます。その後、 ユーザーは出金機能を使用して入金したethを引き出すことができます。ただし、撤退関数には再入可能の脆弱性があります。ユーザーが撤退すると、契約では残高を更新する前に要求された金額がユーザーのアドレスに送金されるため、攻撃者が悪用する機会が生まれます。

さて、攻撃者のスマート コントラクトはどのようになるかを次に示します。

 // Attacker's contract to exploit the reentrancy vulnerability
pragma solidity ^0.8.0;
interface VulnerableContractInterface {    function withdraw(uint256 amount) external;}
contract AttackerContract {    VulnerableContractInterface private vulnerableContract;    address private targetAddress;
    constructor(address _vulnerableContractAddress) {        vulnerableContract = VulnerableContractInterface(_vulnerableContractAddress);        targetAddress = msg.sender;    }
    // Function to trigger the attack    function attack() public payable {        // Deposit some ether to the vulnerable contract        vulnerableContract.deposit{value: msg.value}();
        // Call the vulnerable contract's withdraw function        vulnerableContract.withdraw(msg.value);    }
    // Receive function to receive funds from the vulnerable contract    receive() external payable {        if (address(vulnerableContract).balance >= 1 ether) {            // Reenter the vulnerable contract's withdraw function            vulnerableContract.withdraw(1 ether);        }    }
    // Function to steal the funds from the vulnerable contract    function withdrawStolenFunds() public {        require(msg.sender == targetAddress, "Unauthorized");        (bool success, ) = targetAddress.call{value: address(this).balance}("");        require(success, "Transfer failed");    }} 

攻撃が開始されると:

  1. AttackerContract は、 コンストラクターで VulnerableContract のアドレスを取得し、 それを vulnerableContract 変数に保存します。
  2. 攻撃関数は攻撃者によって呼び出され、デポジット関数を使用して一部の eth を VulnerableContract にデポジットし、すぐにwithdraw< を呼び出します。 VulnerableContract の 関数。
  3. VulnerableContract の引き出し関数は、残高を更新する前に、要求された量の eth を攻撃者の攻撃者コントラクトに転送しますが、攻撃者のコントラクトはその間一時停止されるため、外部呼び出しの場合、関数はまだ完了していません。
  4. 攻撃者コントラクトの受信関数は、 外部呼び出し中に脆弱コントラクトがこのコントラクトに eth を送信したためトリガーされます。
  5. 受信関数は攻撃者コントラクトの残高が少なくとも 1 イーサ (引き出し金額) であるかどうかを確認し、 その引き出しを呼び出して脆弱なコントラクトに再入力します。再び機能します。
  6. ステップ 3 ~ 5 は、VulnerableContract の資金がなくなり、攻撃者のコントラクトに相当量のイーサリアムが蓄積されるまで繰り返されます。
  7. 最後に、攻撃者は攻撃者コントラクトのwithdrawStolenFunds関数を呼び出して、コントラクトに蓄積されたすべての資金を盗むことができます。

ネットワークのパフォーマンスによっては、攻撃が非常に速く発生する可能性があります。イーサリアムからイーサリアムとイーサリアム クラシックへのハードフォークにつながった DAO ハックなどの複雑なスマート コントラクトが関与する場合、攻撃は数時間にわたって発生します。

リエントラント攻撃を防ぐ方法

再入攻撃を防ぐには、安全なスマート コントラクト開発のベスト プラクティスに従って、脆弱なスマート コントラクトを変更する必要があります。この場合、以下のコードのように「チェック - 効果 - インタラクション」パターンを実装する必要があります。

 // Secure contract with the "checks-effects-interactions" pattern
pragma solidity ^0.8.0;
contract SecureContract {    mapping(address => uint256) private balances;    mapping(address => bool) private isLocked;
    function deposit() public payable {        balances[msg.sender] += msg.value;    }
    function withdraw(uint256 amount) public {        require(amount <= balances[msg.sender], "Insufficient balance");        require(!isLocked[msg.sender], "Withdrawal in progress");                // Lock the sender's account to prevent reentrancy        isLocked[msg.sender] = true;
        // Perform the state change        balances[msg.sender] -= amount;
        // Interact with the external contract after the state change        (bool success, ) = msg.sender.call{value: amount}("");        require(success, "Transfer failed");
        // Unlock the sender's account        isLocked[msg.sender] = false;    }} 

この修正バージョンでは、特定のアカウントが出金処理中かどうかを追跡するためのisLocked マッピングを導入しました。ユーザーが出金を開始すると、契約ではアカウントがロックされているかどうか (!isLocked[msg.sender]) が確認され、同じアカウントから現在進行中の他の出金がないことが示されます。

アカウントがロックされていない場合、契約は状態の変更と外部とのやり取りを継続します。状態の変化と外部対話の後、アカウントは再びロック解除され、将来の出金が可能になります。

リエントランシー攻撃の種類

一般に、再入攻撃には、その悪用の性質に基づいて 3 つの主要なタイプがあります。

  1. 単一リエントラント攻撃: この場合、攻撃者が繰り返し呼び出す脆弱な関数は、リエントラント ゲートウェイの影響を受けやすい関数と同じです。上記の攻撃は単一再入攻撃の一例であり、コードに適切なチェックとロックを実装することで簡単に防ぐことができます。
  2. クロスファンクション攻撃: このシナリオでは、攻撃者は脆弱な関数を利用して、脆弱な関数と状態を共有する同じコントラクト内の別の関数を呼び出します。攻撃者によって呼び出される 2 番目の関数は、何らかの望ましい効果をもたらし、悪用の魅力を高めます。この攻撃はより複雑で検出が難しいため、これを軽減するには相互接続された機能全体にわたる厳密なチェックとロックが必要です。
  3. クロスコントラクト攻撃: この攻撃は、外部コントラクトが脆弱なコントラクトと相互作用するときに発生します。この対話中、脆弱なコントラクトの状態は、完全に更新される前に外部コントラクトで呼び出されます。これは通常、複数のコントラクトが同じ変数を共有し、一部のコントラクトが共有変数を安全に更新しない場合に発生します。この攻撃を軽減するには、コントラクト間の安全な通信プロトコルと定期的なスマート コントラクト監査を実装する必要があります。

再入攻撃はさまざまな形で現れる可能性があるため、それぞれを防ぐための特別な対策が必要です。

再入攻撃から安全に保つ

再入攻撃は多大な経済的損失を引き起こし、ブロックチェーン アプリケーションの信頼を損ないました。コントラクトを保護するために、開発者はベスト プラクティスを熱心に採用して再入可能性の脆弱性を回避する必要があります。

また、スマートコントラクトの防御をさらに強化するために、安全な引き出しパターンを実装し、信頼できるライブラリを使用し、徹底的な監査を実施する必要があります。もちろん、新たな脅威に関する情報を常に入手し、セキュリティへの取り組みに積極的に取り組むことで、ブロックチェーン エコシステムの整合性も確実に維持できます。