Vulnerabilidad Reentrancy: Cómo Identificar, Explotar y Prevenir

En el mundo de los contratos inteligentes, la reentrancy se considera una de las vulnerabilidades más peligrosas. Este artículo te ayudará no solo a entender qué es un ataque de reentrancy, sino también cómo prevenirlo de manera efectiva. Desde técnicas básicas hasta soluciones avanzadas, exploraremos formas de proteger todo tu proyecto.

Cómo Funciona la Reentrancy: Mecanismo Básico de Ataque

Para entender la reentrancy, primero debemos captar el concepto básico: un contrato inteligente puede llamar a otro contrato, y en ese momento, el segundo contrato puede volver a llamar al primero mientras aún se está ejecutando.

Imagina que tienes dos contratos: ContractA con 10 Ether y ContractB que ha enviado 1 Ether a él. Cuando ContractB llama a la función de retiro, se verifica si hay suficiente saldo. Si lo hay, se envía Ether de vuelta a ContractB. Aquí, si no hay medidas de protección adecuadas, ese es el punto débil que un atacante puede aprovechar.

En un ataque típico de reentrancy, el atacante necesita dos funciones: attack() para iniciar el ataque, y fallback() para realizar la llamada recursiva. La función fallback es una función especial en Solidity — no tiene nombre ni parámetros, y se llama automáticamente cada vez que Ether es enviado al contrato sin datos.

Paso a Paso del Ataque de Reentrancy

Sigue el proceso del ataque paso a paso. El atacante llama a attack() desde su contrato. Dentro de esta, llama a withdraw() de ContractA.

Cuando ContractA recibe esta llamada, verifica si ContractB tiene saldo > 0. Como tiene 1 Ether, pasa la verificación. Entonces, envía 1 Ether de vuelta a ContractB, activando su fallback. En ese momento, ContractA aún tiene 9 Ether, pero lo más importante: el saldo de ContractB en el registro de ContractA aún no se ha actualizado a 0.

Este es el problema: la función fallback vuelve a llamar a withdraw() de ContractA. ContractA verifica el saldo de ContractB — todavía es 1 Ether. ¿Por qué? Porque la línea balance[msg.sender] = 0 nunca se ejecutó, ya que está después del envío de Ether.

Este proceso se repite: llamada a withdraw() → verificación del saldo (>0) → envío de Ether → fallback activado → llamada a withdraw() otra vez… hasta que se vacíen todos los fondos de ContractA.

Análisis del Código: Cuando la Reentrancy se Hace Realidad

El contrato EtherStore es un ejemplo clásico de contrato vulnerable. Tiene la función deposit() para guardar saldo y withdrawAll() para retirar fondos. El problema está en cómo se implementa withdrawAll(): verifica condiciones, envía Ether, y luego actualiza saldo.

El contrato Attack explota esta vulnerabilidad. En su constructor, el atacante pasa la dirección de EtherStore, permitiéndole llamar a sus funciones. La fallback del contrato Attack se activa cada vez que EtherStore envía Ether, y dentro de ella, continúa llamando a withdrawAll() mientras queden fondos. La función attack() inicia el proceso enviando 1 Ether a EtherStore para pasar la verificación inicial.

El resultado: todos los fondos de EtherStore son drenados en una sola transacción.

Tres Estrategias para Proteger Contratos contra Reentrancy

Para proteger los contratos inteligentes, existen tres niveles de defensa, desde básicos hasta completos.

Modelo noReentrant: Solución Básica de Protección

El método más simple es usar el modificador noReentrant(). Un modificador en Solidity es una función especial que permite alterar el comportamiento de otras funciones sin reescribirlas completamente.

La idea es sencilla: cuando una función está protegida por noReentrant(), bloquea el contrato durante su ejecución. Cualquier llamada que intente volver a entrar en esa función fallará porque la variable de estado de bloqueo lo impide. Solo cuando la función termina y se desbloquea, otras llamadas pueden proceder.

Esta solución es efectiva para proteger una sola función, pero no cubre casos más complejos.

Modelo Check-Effect-Interaction: Prevención para Funciones Múltiples

La segunda técnica, más robusta, es aplicar el patrón Check-Effect-Interaction. En lugar de proteger solo una función, cambia la forma en que escribes la lógica.

El principio clave es: verificar condiciones (Check), actualizar estados inmediatamente después (Effect), y luego interactuar con contratos externos (Interaction). Esto evita que un atacante explote llamadas recursivas, porque al volver a llamar, el saldo ya está actualizado a 0.

En lugar de actualizar balance[msg.sender] = 0 después de enviar Ether, hazlo antes. Así, aunque fallback vuelva a llamar, la verificación fallará porque el saldo ya es 0.

Este método protege contra reentrancy en toda la lógica del contrato, incluso si hay múltiples funciones de retiro.

GlobalReentrancyGuard: Protección Integral en Todo el Proyecto

Para proyectos complejos con múltiples contratos interactuando, se necesita una solución más completa: GlobalReentrancyGuard.

En lugar de bloquear en cada función, esta solución bloquea a nivel de todo el proyecto. Creas un contrato separado que almacena una variable de estado de bloqueo global, y todos los demás contratos hacen referencia a ella.

Imagina este escenario: un atacante llama a una función en el contrato ScheduledTransfer. Tras pasar las verificaciones, envía Ether a AttackTransfer. La fallback de AttackTransfer se activa y intenta llamar a ScheduledTransfer otra vez. Pero, dado que GlobalReentrancyGuard bloquea el estado global, esa llamada se bloquea inmediatamente.

Este método es especialmente útil en proyectos grandes con muchos contratos, donde la reentrancy puede ocurrir entre diferentes contratos.

Elegir la Técnica Adecuada para Tu Proyecto

La elección de la estrategia depende de la complejidad de tu proyecto. Si tu contrato tiene pocas funciones de interacción, noReentrant() es suficiente. Si hay varias funciones de retiro, el patrón Check-Effect-Interaction es recomendable. Para proyectos grandes con múltiples contratos, GlobalReentrancyGuard ofrece protección completa.

Independientemente de la opción que elijas, lo fundamental es entender cómo funciona la reentrancy, para poder detectarla y prevenirla de manera proactiva.

Para actualizaciones diarias sobre seguridad en contratos inteligentes, revisión de código y las últimas tendencias en Web3, sigue recursos especializados en seguridad Solidity.

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
Añadir un comentario
Añadir un comentario
Sin comentarios
  • Anclado