美文网首页合约安全
合约安全:拒绝服务攻击(Denial of Service)

合约安全:拒绝服务攻击(Denial of Service)

作者: 梁帆 | 来源:发表于2022-12-05 11:51 被阅读0次

    一、漏洞

    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.7;
    
    contract KingOfEther {
        address public king;
        uint public balance;
    
        function claimThrone() external payable {
            require(msg.value > balance, "Need to pay more to become the king");
    
            (bool sent, ) = king.call{value: balance}("");
            require(sent, "Failed to send Ether");
    
            balance = msg.value;
            king = msg.sender;
        }
    }
    
    contract Attack {
        KingOfEther kingOfEther;
    
        constructor(KingOfEther _kingOfEther) {
            kingOfEther = KingOfEther(_kingOfEther);
        }
    
        // You can also perform a DOS by consuming all gas using assert.
        // This attack will work even if the calling contract does not check
        // whether the call was successful or not.
        //
        // function () external payable {
        //     assert(false);
        // }
    
        function attack() public payable {
            kingOfEther.claimThrone{value: msg.value}();
        }
    }
    

    这个KingOfEther合约,msg.sender可以通过claimThrone传入以太,当传入的以太数值高于balance的时候,这个msg.sender就成为了king,而且在成为king之前,要把之前king传入的以太返还回去:

    (bool sent, ) = king.call{value: balance}("");
    

    这个合约乍一看没有什么问题,但是可能会导致拒绝服务攻击,核心原因就在于,这个返回以太的代码不一定成功执行,一旦无法成功执行,就阻塞在这里了。

    我们的攻击合约Attack,核心就是进行了一次KingOfEther的合约调用,试想一下这样的过程:

    • Bob通过claimThrone传入了1 Ether,Bob成为King
    • Alice通过claimThrone传入了2 Ether,之前Bob传入的1 Ether又通过call返还了Bob,Alice成为了King
    • 此时Attack合约发布了,再由Attack通过claimThrone传入了4 Ether,之前Alice传入的2 Ether又通过call返还了Alice,Attack地址就成为了King
    • 此时合约已经被锁定了
    • Tom通过claimThrone传入了5 Ether,按照之前的流程,理应由Tom成为新的King,但实际上,当程序执行到返还Attack传入4 Ether时,由于我们的Attack合约并没有receive()或者fallback(),无法接收以太,于是失败了
    • 这样任何新地址都无法成为King,整个合约就是拒绝服务的状态

    二、预防手段

    合约中,不要主动给地址发以太,发以太时也要仔细斟酌整个逻辑,思考会不会发生DoS攻击。像我们这个例子中,就不能主动发送以太,可以自己设置一个withdraw方法,由用户自己进行提款:

    contract KingOfEther {
        address public king;
        uint public balance;
        mapping(address => uint) public balances;
    
        function claimThrone() external payable {
            require(msg.value > balance, "Need to pay more to become the king");
    
            balances[king] += balance;
    
            balance = msg.value;
            king = msg.sender;
        }
    
        function withdraw() public {
            require(msg.sender != king, "Current king cannot withdraw");
    
            uint amount = balances[msg.sender];
            balances[msg.sender] = 0;
    
            (bool sent, ) = msg.sender.call{value: amount}("");
            require(sent, "Failed to send Ether");
        }
    }
    

    相关文章

      网友评论

        本文标题:合约安全:拒绝服务攻击(Denial of Service)

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