美文网首页
智能合约安全-self destruct漏洞

智能合约安全-self destruct漏洞

作者: 张亚伦 | 来源:发表于2022-07-20 10:50 被阅读0次

    背景:由于公链环境下所有的信息都是共享的,智能合约相当于是完全透明化,任何人都可以调用,外加一些利益的驱动,导致引发了很多hacker的攻击。其中self destruct攻击也是常见的攻击方式之一。

    目标:将目标合约瘫痪掉,无法做正常的业务。

    前言

    在进入正题之前,我先带大家从基础知识点开始,然后一点点深入到怎么攻击以及预防。
    好,废话不多说,先看下selfdestruct的官方解释:

    selfdestruct(address payable recipient)
    Destroy the current contract, sending its funds to the given Address and end execution
    理解起来也很简单,就是说合约销毁的时候,可以把ether转到指定的地址。

    常见的用法 : 当我们的合约有漏洞或者业务变更的变化时,需要把它销毁掉,以避免造成更多的影响。此时可以在合约里提供一个销毁方法;

    示例如下:

    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);
            }
        }
    }
    

    当需要销毁时,合约的owner可以调用destory()方法进行合约销毁。

    那接下来,我们正式进入主题,如何使用self-destory进行攻击。

    攻击演示

    1. 合约示例
    演示需要用到的两个合约,一个模拟业务合约,一个为攻击合约。

    业务合约:一个简单的游戏,每个用户每次可以存放1ether到合约里,等到第7次存放的用户将成为赢家,可以把7ether提到自己账户里。

    攻击合约:编写了一个合约销毁的方法,即本合约销毁时会发送ether到指定的合约。

    攻击逻辑:调用攻击合约的attack方法,使得上述的业务合约余额超过7。

    示例如下:

    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. 合约部署
    老规矩,我们使用remix进行部署测试。

    部署成功后,截图如下:


    合约合约 攻击合约

    3. 正常业务操作

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

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


    image.png

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


    B账户余额 1657617049093.jpg

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

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

    业务合约.png

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


    攻击合约.png

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

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

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

    image.png

    解决方案

    最后,给大家推荐一个常用的方案:将全局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;
        }
    }
    
    

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

    相关文章

      网友评论

          本文标题:智能合约安全-self destruct漏洞

          本文链接:https://www.haomeiwen.com/subject/xccabrtx.html