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);
}
}
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 thesendFunds()
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));
}
}
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);
}
}
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)