# 智能合约安全-selfdestruct 攻击

By [0xEe20](https://paragraph.com/@0xee20) · 2022-05-18

---

selfdestruct函数可以在执行时强制向目标地址转账，从而影响目标合约的正常业务逻辑。（比如合约有基于address(this).balance的业务逻辑）

下面是一个经典的例子：

    // SPDX-License-Identifier: MIT
    
    // The goal of this game is to be the 7th player to deposit 1 Ether.
    // Players can deposit only 1 Ether at a time.
    // Winner will be able to withdraw all Ether.
    
    /*
    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.
    */
    
    pragma solidity ^0.8.10;
    
    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");
        }
    }
    
    contract Attack {
        EtherGame etherGame;
    
        constructor(EtherGame _etherGame) {
            etherGame = EtherGame(_etherGame);
        }
    
        function attack() 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);
        }
    }
    

注释里有合约目的的说明，不再赘述了

代码放到remix中执行，

无论产生winner前有多少人调用过deposit（），

在使用攻击合约Attack.attack()调用seldestruct时，发送代币数量选择任意可以让合约余额大于等于7的值。

这样即可越过deposit()业务逻辑，直接让address(this).balance的值大于7。

因为deposit()中require(balance <= targetAmount, "Game is over");

限制balance要不超过7，所以后来者也无法再次存款，

已经打过款的人也无法claimReward。

合约从而瘫痪，永远无法产生winner

防止攻击方法：

别直接用全局address(this).balance了呗！用变量重新统计进入deposit逻辑的代币数。

        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;
            }
        }

---

*Originally published on [0xEe20](https://paragraph.com/@0xee20/selfdestruct)*
