Details

Solidity中的selfdestruct 函数提供了一种合约终止和余额转移的机制,它可能会导致强制将以太注入到目标合约中,改变其状态并破坏其逻辑。

Example

solidityCopy code
pragma solidity ^0.8.20;

contract EtherGame {
    uint public targetAmount = 7 ether;
    address public winner;

    function deposit() public payable {
        require(msg.value == 1 ether, "You can only send 1 Ether");

        uint balance = address(this).balance;
        require(balance <= targetAmount, "Game is over");

        if (balance == targetAmount) {
            winner = msg.sender;
        }
    }

    function claimReward() public {
        require(msg.sender == winner, "Not winner");

        (bool sent, ) = msg.sender.call{value: address(this).balance}("");
        require(sent, "Failed to send Ether");
    }
}

在这个合约中它使用了address(this).balance ,这一步会很危险,因为攻击合约可以通过selfdestruct强制将余额转给这个合约,导致没人能赢这个游戏

缓解策略就是不依赖 address(this).balance 来保护关键游戏逻辑,从而防止攻击。相反,使用自我维护的状态变量来跟踪存入的以太币。

solidityCopy code
pragma solidity ^0.8.20;

contract EtherGame {
    uint public targetAmount = 7 ether;
    uint public balance;
    address public winner;

    function deposit() public payable {
        require(msg.value == 1 ether, "You can only send 1 Ether");

        balance += msg.value;
        require(balance <= targetAmount, "Game is over");

        if (balance == targetAmount) {
            winner = msg.sender;
        }
    }

    function claimReward() public {
        require(msg.sender == winner, "Not winner");

        (bool sent, ) = msg.sender.call{value: balance}("");
        require(sent, "Failed to send Ether");
    }
}

攻击模板:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;

contract SelfDestructable {
  // `terminate` will make the contract uncallable 
  function terminate(address sendTo) public {
      selfdestruct(payable(sendTo));
  }

  receive() external payable {
    return;
  }
}

References

https://immunebytes.com/blog/self-destruct-exploit-forced-ether-injection-in-solidity-contracts/

https://r4bbit.vercel.app/blog/force-feeding-attacks-in-solidity