8.1 Reentrancy y cómo prevenirlo




Curso de Solidity: Reentrancy y Cómo Prevenirlo

Punto del Curso de Solidity: 8.1 Reentrancy y Cómo Prevenirlo

Uno de los problemas más críticos en la programación de contratos inteligentes en Solidity es la vulnerabilidad de reentrancy. Esta vulnerabilidad permite a un atacante llamar recursivamente a una función del contrato antes de que se complete la ejecución de esa función.

¿Qué es la reentrancy

La vulnerabilidad de reentrancy ocurre cuando un contrato llama a un contrato externo antes de actualizar su estado. Si el contrato externo tiene una función de callback que llama de nuevo a la función original del contrato, puede continuar ejecutándose antes de actualizar el estado del contrato original. Este bucle puede seguir infinitamente, llevándose todos los fondos del contrato original.

Ejemplo de ataque de reentrancy

Consideremos un contrato de tipo banco que permite a los usuarios depositar y retirar Ether. Aquí hay una versión vulnerable de ese contrato:

pragma solidity ^0.8.0

contract VulnerableBank {
    mapping(address => uint256) public balances

    function deposit() public payable {
        balances[msg.sender]  = msg.value
    }

    function withdraw(uint256 _amount) public {
        // Verifica que el usuario tiene suficientes fondos
        require(balances[msg.sender] >= _amount, No hay suficientes fondos)

        // Enviar Ether al usuario
        (bool success, ) = msg.sender.call{value: _amount}()
        require(success, Transferencia fallida)

        // Actualizar el balance del usuario
        balances[msg.sender] -= _amount
    }
}

El problema en este contrato es que está interactuando con un contrato externo (por ejemplo, al enviar Ether a msg.sender) antes de actualizar su propio estado. Un atacante puede escribir un contrato malicioso que explote esta vulnerabilidad.

pragma solidity ^0.8.0

import ./VulnerableBank.sol

contract Attack {
    VulnerableBank public vulnerableBank

    constructor(address _vulnerableBankAddress) {
        vulnerableBank = VulnerableBank(_vulnerableBankAddress)
    }

    // Fallback function que permitirá la reentrancy
    fallback() external payable {
        if(address(vulnerableBank).balance >= 1 ether) {
            vulnerableBank.withdraw(1 ether)
        }
    }

    function attack() public payable {
        require(msg.value >= 1 ether, Necesita al menos 1 ether para atacar)
        vulnerableBank.deposit{value: 1 ether}()
        vulnerableBank.withdraw(1 ether)
    }
}

En este ejemplo, el contrato Attack puede utilizar la función de retirada para continuar llamando a la misma función hasta que vacíe el contrato VulnerableBank.

Cómo prevenir la reentrancy

Hay varias formas de mitigar los ataques de reentrancy en Solidity:

  1. Patrón de Retiro: Invertir la lógica de las funciones de retiro para que los usuarios retiren sus fondos solicitando un retiro y luego retirando en una transacción separada.
  2. Actualizando el estado antes de la interacción externa: Actualizar el estado del contrato antes de interactuar con un contrato externo.
  3. Uso de ReentrancyGuard: Emplear la biblioteca OpenZeppelin ReentrancyGuard para prevenir reentrancy.

Ejemplo de prevención usando ReentrancyGuard

Aquí hay una versión más segura del contrato VulnerableBank utilizando la biblioteca ReentrancyGuard:

pragma solidity ^0.8.0

import @openzeppelin/contracts/security/ReentrancyGuard.sol

contract SecureBank is ReentrancyGuard {
    mapping(address => uint256) public balances

    function deposit() public payable {
        balances[msg.sender]  = msg.value
    }

    function withdraw(uint256 _amount) public nonReentrant {
        // Verifica que el usuario tiene suficientes fondos
        require(balances[msg.sender] >= _amount, No hay suficientes fondos)

        // Actualizar el balance del usuario antes de enviar Ether
        balances[msg.sender] -= _amount

        // Enviar Ether al usuario
        (bool success, ) = msg.sender.call{value: _amount}()
        require(success, Transferencia fallida)
    }
}

En este caso, el modificador nonReentrant de la biblioteca ReentrancyGuard se utiliza para evitar que la función withdraw sea llamada recursivamente.

Utilizando una combinación de buenas prácticas como actualizar el estado antes de las interacciones externas, emplear patrones de retiro y utilizar bibliotecas de terceros como OpenZeppelin, se pueden mitigar eficazmente los ataques de reentrancy en contratos Solidity.


AnteriorSiguiente

[mwai_chat]

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *