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 OpenZeppelinReentrancyGuardpara 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