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:
- 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.
- Actualizando el estado antes de la interacción externa: Actualizar el estado del contrato antes de interactuar con un contrato externo.
-
Uso de
ReentrancyGuard
: Emplear la biblioteca OpenZeppelinReentrancyGuard
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.
Deja una respuesta