Sending Ether Without Getting Hacked


The previous blog post discusses how the reentrancy attack was occurring. Because withdrawFromAttackee() was zero-ing out the balance after the call to send the Ether, the Attacker’s fallback() could exploit it by continuously withdrawing before the balance was actually zero-ed out.

Below is the updated code that stops this reentrancy attack…
// SPDX-License-Identifier: GPL-3.0

pragma solidity >=0.7.0 <0.9.0;

contract Attackee {
    mapping(address => uint) public attackeeBalances; 
    
    function depositIntoAttackee() external payable {
        attackeeBalances[msg.sender] += msg.value;
    }
    
    function withdrawFromAttackee() public {
        uint senderBalance = attackeeBalances[msg.sender];
        require(senderBalance > 0);
        attackeeBalances[msg.sender] = 0;
        
        (bool success, ) = address(msg.sender).call{ value: senderBalance }("");
        require(success, "withdrawFromAttackee failed to send");
    }
    
    function getBalanceFromAttackee() public view returns (uint) {
        return address(this).balance;
    }
}

contract Attacker {
    Attackee public contractToAttack;
    
    constructor(address _contractToAttackAddress) {
        contractToAttack = Attackee(_contractToAttackAddress);
    }
    
    //this is called when Attackee sends Ether to this contract (Attacker)
    fallback() external payable {
        //comment this out to allow the withdrawal
        if(address(contractToAttack).balance >= 1 ether) {
            contractToAttack.withdrawFromAttackee();
        }
    }
    
    function depositIntoAttackee() public payable {
        require(msg.value >= 1 ether);
        contractToAttack.depositIntoAttackee{value: msg.value}();
    }
    
    function performAttack() public {
        contractToAttack.withdrawFromAttackee();
    }
    
    function getBalanceFromAttacker() public view returns (uint) {
        return address(this).balance;
    }

Take the code over to Remix and try it out.

Here’s how to test this code…

Step 1

  • Deploy Attackee
  • Deposit 10 ether into Attackee
  • Use Attackee address to deploy Attacker
  • Deposit 2 ether via the Attacker contract
  • Copy the Attacker address into ‘attackeeBalances’ (on Attackee)
  • Click ‘attackeeBalances’ to see the Attacker’s balance
  • Click ‘getBalanceFromAttackee’ to see the whole balance

Step 2

Now click the button to Perform the Attack in the Attacker contract, and re-check the balances.

The transaction reverted, now let’s let it go through. Comment out the code inside of the Attacker’s fallback()

Save the file and repeat Step 1.

Click ‘Perform Attack’ in Attacker, and re-check the balances.
Note that ‘attackeeBalances’ is now 0, but the contract still has 10 ether.




Author:WebSculpt




Post a Comment

0 Comments