DEV Community

Rushank Savant
Rushank Savant

Posted on

Force Send ETH - 2

In the last post Force Send ETH - 1, we understood about selfdestruct and how it's used to forcefully send ETH to any contract.

Now let's take an example to understand what's the vulnerability in this. Consider the following contract:

contract Crowdfund {
    // this contract only accepts a certain amount of funds
    // if someone tries to send an amount that exceeds contract balance more than the limit, tnx fails
    // funds are transfered to an address only when contract balance equals fund limit

    uint fundLimit = 3 ether;
    bool contractOpen = true;

    function donate() external payable { // for others to donate eth to this contract
        require(contractOpen, "Contract has stopped recieving funds");
        require(address(this).balance <= fundLimit, "Can't send specified amount");
        // note: we cannot do address(this).balance + msg.value, because address(this).balance already takes msg.value
    }

    function getBalance() external view returns(uint) { // to get current balance of this contract
        return address(this).balance;
    } 

    function sendFunds() external { // to send all collected funds
        require(contractOpen, "Contract has stopped recieving funds");
        require(address(this).balance == fundLimit, "Fund limit not reached yet");
        contractOpen = false; // contract closed
        payable(address(0x78731D3Ca6b7E34aC0F824c42a7cC18A495cabaB)).transfer(address(this).balance);
    }
}
Enter fullscreen mode Exit fullscreen mode

Now consider the following scenario:

  • Contract balance has reached 3 ether, but some hacker force-sends extra ETH to CrowdFund contract address.
  • Now contract balance > fundLimit, hence require(address(this).balance == fundLimit, "Fund limit not reached yet"); from the sendFunds() function will fail and not let anyone send the collected funds to required receiver.

Hope you got an idea about what exactly the vulnerability is.

Let's see the attacker contract:

contract Attacker{
    fallback() external payable {

    }

    function attack(address _crowdFund) external {
        selfdestruct(payable(_crowdFund));
    }
}
Enter fullscreen mode Exit fullscreen mode

Till now we understood what exactly this vulnerability is and how hackers can take advantage of it. Let's now see how to avoid such hacks.

  • one solution is to change

require(address(this).balance == fundLimit, "Fund limit not reached yet"); to

require(address(this).balance >= fundLimit, "Fund limit not reached yet"); in sendFunds() function of Crowdfund contract.

But what if that's an important condition for some application?

  • better solution is to avoid using address(this) to track funds

Let's see the following contract to understand this better:

contract CrowdFund_safe{
    uint fundLimit = 3 ether;
    bool contractOpen = true;
    uint balance = 0;

    function donate() external payable { // for others to donate eth to this contract
        require(contractOpen, "Contract has stopped recieving funds");
        require(balance + msg.value <= fundLimit, "Can't send specified amount");
        balance += msg.value;
    }

    function getBalance() external view returns(uint) { // to get current balance of this contract
        return address(this).balance;
    } 

    function sendFunds() external { // to send all collected funds
        require(contractOpen, "Contract has stopped recieving funds");
        require(balance == fundLimit, "Fund limit not reached yet");
        contractOpen = false; // contract closed
        payable(address(0x78731D3Ca6b7E34aC0F824c42a7cC18A495cabaB)).transfer(balance);
    }
}
Enter fullscreen mode Exit fullscreen mode

Now even if hacker force-sends ETH to this contract, it won't change the balance state variable, hence this won't affect any conditions in the contract.
Hence the contract is safe from such attacks.

Top comments (0)