Ataques de reentrancia en Contratos inteligentes: Comprendiendo la vulnerabilidad e implementando estrategias de prevención

Los ataques de reentrancia representan una de las vulnerabilidades más notorios en la seguridad de contratos inteligentes, responsables de millones en fondos robados a través de varios protocolos de blockchain. Este artículo explora la mecánica detrás de las vulnerabilidades de reentrancia y presenta técnicas de prevención completas que los desarrolladores conscientes de la seguridad deberían implementar.

¿Qué es un ataque de reentrancia?

En su esencia, un ataque de reentrada ocurre cuando una función en un contrato inteligente se llama repetidamente antes de que se complete su ejecución previa. La vulnerabilidad fundamental surge cuando un contrato inteligente llama a un contrato externo antes de resolver sus propios cambios de estado, creando una oportunidad para la explotación.

En un escenario típico, el Contrato A interactúa con el Contrato B al llamar a una de sus funciones. La falla crítica de seguridad surge cuando el Contrato B gana la capacidad de llamar de vuelta al Contrato A mientras el Contrato A aún está ejecutando su función original. Este patrón de interacción recursiva crea la base para una posible explotación.

Cómo funcionan los ataques de reentrada: un desglose paso a paso

Considera este escenario: el Contrato A tiene un total de 10 ETH, y el Contrato B ha depositado 1 ETH en el Contrato A. La vulnerabilidad se vuelve explotable cuando el Contrato B intenta retirar sus fondos a través de la siguiente secuencia:

  1. El contrato B llama a la función withdraw() en el contrato A
  2. El contrato A verifica que el saldo del contrato B es mayor que 0 (pasa la verificación)
  3. El contrato A envía el ETH al contrato B, activando la función de fallback del contrato B
  4. Antes de que el Contrato A pueda actualizar el saldo del Contrato B a cero, la función de retroceso en el Contrato B llama a withdraw() nuevamente.
  5. El contrato A verifica el saldo del contrato B, que aún muestra 1 ETH (no actualizado)
  6. El proceso se repite hasta que se agoten los fondos del Contrato A.

La vulnerabilidad clave es que la actualización del saldo ocurre después de la transferencia de ETH, lo que permite al atacante explotar el mismo saldo varias veces antes de que se establezca en cero.

Anatomía de un Ataque: Implementación Técnica

Examinemos el patrón de código vulnerable:

solidity // Contrato EtherStore vulnerable contrato EtherStore { mapping(address => uint) saldo público;

función depositar() pública pagable {
    balances[msg.sender] += msg.value;
}

función retirarTodo() pública {
    uint bal = balances[msg.sender];
    require(bal > 0);
    
    // Vulnerabilidad: Llamada externa antes de la actualización del estado
    (bool enviado, ) = msg.sender.call{value: bal}("");
    require(sent, "Error al enviar Ether");
    
    // La actualización del estado ocurre demasiado tarde
    balances[msg.sender] = 0;
}

}

Ahora, veamos cómo un atacante podría explotar esta vulnerabilidad:

solidez // Contrato de ataque que explota la vulnerabilidad de reentrancia contrato Ataque { EtherStore público etherStore;

constructor(direccion _etherStoreAddress) {
    etherStore = EtherStore(_direcciónEtherStore);
}

// Función de reserva que se llama cuando EtherStore envía Ether
recibir() externo pagable {
    if(address(etherStore).balance >= 1 ether) {
        // Vuelva a ingresar la función withdrawAll
        etherStore.retirarTodo();
    }
}

función attack() externo pagable {
    require(msg.value >= 1 ether);
    
    // Depositar para establecer un saldo en EtherStore
    etherStore.deposit{value: 1 ether}();
    
    // Iniciar el proceso de retiro, provocando el ataque de reentrada
    etherStore.retirarTodo();
}

}

La secuencia de ataque comienza cuando el atacante llama a attack(). Esto deposita 1 ETH para establecer un saldo y luego llama a withdrawAll(). Cuando EtherStore envía ETH de vuelta, se activa la función receive() en el contrato de ataque, que llama a withdrawAll() nuevamente antes de que se actualice el saldo. Este bucle continúa hasta que EtherStore se queda sin fondos.

Técnicas de Prevención Integral

Los desarrolladores conscientes de la seguridad pueden implementar tres técnicas robustas para protegerse contra las vulnerabilidades de reentrada:

1. Protección a Nivel de Función: El Modificador nonReentrant

La protección más común a nivel de función individual es implementar un guardia de reentrancia:

solidez contrato ReentrancyGuard { bool privado bloqueado = falso;

modificador nonReentrant() {
    require(!locked, "Llamada reentrante");
    bloqueado = true;
    _;
    bloqueado = falso;
}

// Aplica este modificador a las funciones vulnerables
función retirarFondos() público noReentrante {
    // Código de función protegido contra reentrada
}

}

Este enfoque bloquea el contrato de manera efectiva durante la ejecución de la función, evitando cualquier llamada recursiva hasta que la función se complete y desbloquee el estado.

2. Protección de Cruz-Funciones: Patrón de Comprobaciones-Effectos-Interacciones

Este patrón de seguridad fundamental reestructura el código para eliminar vulnerabilidades en múltiples funciones:

solidez // IMPLEMENTACIÓN VULNERABLE función retirarTodo() público { uint bal = balances[msg.sender]; require(bal > 0);

(bool enviado, ) = msg.sender.call{value: bal}("");
require(sent, "Error al enviar Ether");

balances[msg.sender] = 0; // Actualización de estado después de la interacción

}

// IMPLEMENTACIÓN SEGURA función withdrawAll() pública { uint bal = balances[msg.sender]; require(bal > 0); // Verifica

balances[msg.sender] = 0; // Efectos (cambios de estado)

(bool enviado, ) = msg.sender.call{value: bal}(""); // Interacciones
require(sent, "Error al enviar Ether");

}

Al seguir el patrón Checks-Effects-Interactions, el contrato actualiza su estado antes de cualquier interacción externa, asegurando que incluso si un atacante intenta volver a entrar, se encontrará con el estado actualizado (saldo cero).

3. Protección a Nivel de Proyecto: Guardián Global de Reentrancia

Para proyectos complejos con múltiples contratos interactuantes, implementar un guardia de reentrancia global proporciona protección a nivel del sistema:

solidez contrato GlobalReentrancyGuard { bool privado _notEntered = true;

modificador globalNonReentrant() {
    require(_noIngresado, "ReentrancyGuard: llamada reentrante");
    _notEntered = false;
    _;
    _notEntered = true;
}

}

// Todos los contratos en el proyecto heredan de GlobalReentrancyGuard contrato SecureContract es GlobalReentrancyGuard { función vulnerableOperation() pública globalNonReentrant { // Protegido contra reentrancias en todo el proyecto } }

Este enfoque es particularmente valioso para proteger contra ataques de reentrada entre contratos en protocolos DeFi donde múltiples contratos interactúan entre sí.

Impacto en el Mundo Real de las Vulnerabilidades de Reentrada

Las vulnerabilidades de reentrada han llevado a algunos de los exploits más devastadores en la historia del blockchain. El infame hackeo de DAO en 2016 resultó en el robo de aproximadamente 3.6 millones de ETH (, que valían alrededor de $50 millones en ese momento ) y, en última instancia, condujo al hard fork de Ethereum que creó Ethereum Classic.

Más recientemente, en 2020, el protocolo Lendf.Me perdió aproximadamente $25 millones debido a un ataque de reentrada, destacando que a pesar de una mayor conciencia, estas vulnerabilidades continúan representando riesgos significativos para la seguridad de los contratos inteligentes.

Mejores Prácticas de Seguridad

Más allá de las técnicas específicas mencionadas, los desarrolladores deben seguir estas prácticas de seguridad adicionales:

  1. Siempre utiliza la última versión de Solidity que ofrece características de seguridad mejoradas
  2. Los contratos sujetos a auditorías de seguridad exhaustivas por empresas de renombre
  3. Implementar una cobertura de pruebas integral que incluya pruebas unitarias para casos límite
  4. Comience con una exposición mínima de ETH al implementar nuevos contratos en producción
  5. Considere la verificación formal para contratos de alto valor

Al implementar estas técnicas defensivas y seguir las mejores prácticas de seguridad, los desarrolladores pueden reducir significativamente el riesgo de ataques de reentrada y construir contratos inteligentes más seguros para aplicaciones de blockchain.

ETH0.47%
ETC0.43%
Ver originales
Esta página puede contener contenido de terceros, que se proporciona únicamente con fines informativos (sin garantías ni declaraciones) y no debe considerarse como un respaldo por parte de Gate a las opiniones expresadas ni como asesoramiento financiero o profesional. Consulte el Descargo de responsabilidad para obtener más detalles.
  • Recompensa
  • Comentar
  • Republicar
  • Compartir
Comentar
0/400
Sin comentarios
  • Anclado
Opera con criptomonedas en cualquier momento y lugar
qrCode
Escanee para descargar la aplicación Gate
Comunidad
Español
  • 简体中文
  • English
  • Tiếng Việt
  • 繁體中文
  • Español
  • Русский
  • Français (Afrique)
  • Português (Portugal)
  • Bahasa Indonesia
  • 日本語
  • بالعربية
  • Українська
  • Português (Brasil)