Home Web3 SecuritySmart Contract Audit Self-Destruct Exploit: Forced Ether Injection in Solidity Contracts

Self-Destruct Exploit: Forced Ether Injection in Solidity Contracts

by ImmuneBytes
Self-Destruct Exploit: Forced Ether Injection in Solidity Contracts

Introduction

The selfdestruct function in Solidity provides a mechanism for contract termination and balance transfer, but it comes with inherent security risks. One of the prominent vulnerabilities it can introduce is forced ether injection into a target contract, altering its state and potentially breaking its logic. This attack vector examines how a smart contract can be vulnerable to such an attack and how to mitigate it.

Understanding Self-Destruct in Solidity

In Solidity, the selfdestruct(address payable recipient) function transfers all Ether from the contract to a specified recipient address and removes the contract’s bytecode, rendering it inoperative. If the recipient address is a contract, it receives the Ether without any of its functions being called, not even the fallback function.

Attack Scenario: Breaking EtherGame

Let’s look at an example contract called EtherGame, which is based on Solidity by example.

solidityCopy code
pragma solidity ^0.8.20;

contract EtherGame {
    uint public targetAmount = 7 ether;
    address public winner;

    function deposit() public payable {
        require(msg.value == 1 ether, "You can only send 1 Ether");

        uint balance = address(this).balance;
        require(balance <= targetAmount, "Game is over");

        if (balance == targetAmount) {
            winner = msg.sender;
        }
    }

    function claimReward() public {
        require(msg.sender == winner, "Not winner");

        (bool sent, ) = msg.sender.call{value: address(this).balance}("");
        require(sent, "Failed to send Ether");
    }
}

In this simple game, players send 1 Ether to the contract, aiming to be the one who makes the total balance reach 7 Ether. The first player to do so becomes the winner and can claim the reward.

Vulnerability Details

An attacker can create another contract with a selfdestruct function and send enough Ether to break the game logic. For example, sending 5 Ether to the contract will make its balance reach or exceed the target amount, thereby making it impossible for any player to win the game.

Mitigation Strategies

The attack can be prevented by not relying on address(this).balance for critical game logic. Instead, use a self-maintained state variable to track the deposited Ether.

Here’s a safer version of the EtherGame contract:

solidityCopy code
pragma solidity ^0.8.20;

contract EtherGame {
    uint public targetAmount = 7 ether;
    uint public balance;
    address public winner;

    function deposit() public payable {
        require(msg.value == 1 ether, "You can only send 1 Ether");

        balance += msg.value;
        require(balance <= targetAmount, "Game is over");

        if (balance == targetAmount) {
            winner = msg.sender;
        }
    }

    function claimReward() public {
        require(msg.sender == winner, "Not winner");

        (bool sent, ) = msg.sender.call{value: balance}("");
        require(sent, "Failed to send Ether");
    }
}

In this revised version, we keep track of the deposited Ether with our own balance state variable. This makes the contract resilient against forced Ether injections via selfdestruct.

Conclusion

While the selfdestruct function has legitimate uses; it can be a significant security risk if misused or misunderstood. It is crucial to be aware of its implications and to follow best practices in smart contract development to mitigate associated risks.

You may also like