Un desconocido apuntó el módulo
Una Gnosis Safe perdió 7,8 millones de dólares en ETH restakeado — no porque la Safe fallara, sino porque un módulo habilitado dejó que un keeper cualquiera lo apuntara a la pool del propio atacante. La clase es vieja. La lección está en de dónde dejas que venga un destino.
El 15 de septiembre de 2026, un usuario de Ethereum perdió cerca de 7,8 millones de dólares en ETH restakeado de una cartera multifirma. Hay un giro memorable —un bot de front-running se llevó el dinero antes que el propio atacante— pero el giro no es la lección. La lección son los cuatro segundos previos, cuando la cartera hizo exactamente lo que se le ordenó, ordenado por alguien que no debería haber podido ordenarle nada.
Esta es la forma del ataque, porque es una de las maneras más comunes de vaciar una cuenta inteligente y la parte defectuosa siempre parece correcta.
Lo que pasó en realidad
La víctima tenía unos 2.900 rsETH —un token de restaking líquido— dentro de una Gnosis Safe, en su forma envuelta por Aave. El atacante nunca tuvo una clave.
En cambio, usó una llamada pública de keeper para apuntar un módulo de liquidez de Uniswap V4 —un módulo que la cuenta había habilitado— a una pool que el atacante acababa de crear. Esa pool llevaba un hook malicioso. El hook desenvolvió la posición en rsETH libremente transferible, y el módulo, actuando con la autoridad de la Safe, lo movió.
El fallo está en de dónde vino el destino
Quita el restaking y los hooks y queda una sola cláusula. A un módulo de confianza le pasaron un destino —qué pool, qué contrato— quien lo llamaba, y actuó sobre ese destino desde dentro de su propio contexto privilegiado. La validación que hizo no bastaba, porque el atacante podía elegir lo único que decidía a dónde iba el dinero.
La pool por la que enrutar. Una dirección de contrato, elegida en el momento de la llamada, por un keeper público que cualquiera puede invocar.
Nada que decida un destino. El sitio se nombra desde una lista que instaló el propietario; el receptor es la cuenta, siempre; el módulo compone la llamada.
Un módulo que toma su destino de quien lo llama es un módulo que confía en que quien lo llama lo apunte bien. En una ruta de keeper público, quien llama es el público. El atacante no entró por la fuerza. Le pusieron el volante en las manos y le pidieron que fuera amable.
El giro, ya que es bueno
La transacción del exploit entró en el mempool público —la sala de espera donde las transacciones no confirmadas están a la vista. Un bot generalizado de front-running llamado Yoink vio el drenaje pendiente, lo copió con una comisión más alta y ejecutó primero en el mismo bloque. El atacante original no se llevó nada.
¿Puede pasar esto en una cuenta de DeFiLoops?
Es la pregunta que de verdad hace un lector en nuestro lugar, así que esta es la respuesta honesta: el movimiento concreto —apuntar un módulo de confianza al contrato de un atacante— no se puede expresar contra una cuenta de DeFiLoops, y no por suerte sino por diseño. No porque nuestras comprobaciones sean más cuidadosas. Porque al agente nunca se le entrega un destino que apuntar.
- Elegir la pool / contrato por el que enrutar
- No existe ese campo. El agente aporta parámetros tipados; el adaptador compone la llamada y elige el destino.
- Nombrar un sitio por dirección
- Rechazado. Los sitios se nombran desde el conjunto que instaló el propietario, nunca como una dirección en la instrucción.
- Redirigir el pago a ti mismo
- El receptor se fuerza a la cuenta, leído desde la cuenta — nunca tomado de la instrucción.
- Demostrar que te referías a esta cuenta
- El propietario se lee desde la cuenta. Una firma válida del agente no prueba que se refería a la cuenta, ni al destino, correctos.
La razón de fondo está un nivel por encima. Una cuenta de DeFiLoops en producción no da al agente ninguna ruta de llamada directa. La Safe drenada tenía un módulo que podía ejecutar una llamada elegida por quien llamaba. Nuestro agente no puede componer una llamada. Aporta un importe, un sitio nombrado desde una lista, un rango — y un adaptador en la lista blanca construye la transacción. No hay contrato aportado, porque el agente nunca aporta uno.
El plan del atacante, paso a paso
Supón el atacante realista más fuerte sin robar tu clave de propietario: tiene la clave del agente, la que firma las instrucciones cotidianas de la cuenta. Esto es lo que intentaría, y dónde termina cada paso.
- Apuntar un módulo a una pool que controlas
No hay ningún módulo que tome una pool de quien lo llama. El trading pasa por adaptadores que leen el sitio desde el conjunto instalado por el propietario. Una pool creada por el atacante no está en él, y una dirección en la instrucción se rechaza como llamada arbitraria.
- Recurrir a una llamada directa y elegir tu propio destino
En producción no hay ruta de llamada directa a la que recurrir. No está instalada. El movimiento no tiene dónde escribirse.
- Enrutar por un adaptador legítimo y redirigir los fondos
El receptor se fuerza a la cuenta, el propietario se lee desde la cuenta, el sitio viene del conjunto del propietario y el gasto está limitado por periodo. Cada destino lo fija el adaptador, no la instrucción.
- Instalar un módulo propio, o cambiar la raíz de la cuenta
Esos selectores se rechazan. El agente nunca puede instalar el tipo de módulo redirigible que drenó la Safe, convertirse en el propietario ni entregar la cuenta.
- Mientras tanto, el propietario revoca al agente
Una transacción, sin necesitar la cooperación de nadie, y toda instrucción futura de esa clave se rechaza antes incluso de inspeccionar su destino. La clave robada muere en cuanto el propietario actúa.
Una clave de agente robada, contra una cuenta en producción, no puede apuntar nada a un atacante ni tomar el control. Esa es toda la afirmación de seguridad, y se sostiene contra este mismo ataque.
Lo que no afirmamos
Una entrada de seguridad que termine ahí está vendiendo algo. El límite honesto tiene tres bordes, y ninguno es este ataque.
La parte que cambió nuestra propia confianza
Nos propusimos demostrar que el guard bloquea esto. Escribir esa demostración reveló que las pruebas que ya teníamos no lo demostraban — revertían una capa demasiado pronto, en la fontanería de la cuenta, y pasaban porque el framework de pruebas emparejaba mal el revert. La protección era real. La evidencia de ella era teatro.
Una prueba de seguridad que no puede fallar cuando rompes aquello que protege no es una prueba débil. Es un adorno, y es más peligrosa que ninguna prueba, porque se lee como cobertura.
Así que reconstruimos la demostración contra el guard directamente, afirmando el motivo exacto por el que se rechaza cada llamada, y luego rompimos cada control a propósito para ver fallar la prueba correspondiente. Quitar la línea de auto-referencia hace fallar su prueba. Anular la ruta de revocación hace fallar otra. Esa es la diferencia entre creer que un límite se sostiene y saberlo.
Cómo evaluar cualquier afirmación así
No hace falta que confíes en esta página. La pregunta que separa un límite real de un filtro es la de siempre: ¿qué puede escribir el agente, o un keeper de paso?
Si la respuesta incluye un destino —una pool, un contrato, una dirección que quien llama elige— entonces toda salvaguarda a su alrededor es una comprobación entre el destino elegido por un desconocido y tu dinero, y las comprobaciones se juzgan contra los ataques que alguien ya imaginó. El drenaje de septiembre es lo que cuesta un destino elegido por quien llama.
Si la respuesta es parámetros tipados y nada más, el ataque de esta entrada no tiene dónde aterrizar, porque aquello que explota —un destino que puedes nombrar— nunca se entregó.
Fuentes: informes de monitorización de PeckShieldAlert y Blockaid (15 de septiembre de 2026), recogidos por PANews, Odaily, Foresight News y BlockBeats; transacciones on-chain vía Etherscan. Las cifras y el mecanismo son los reportados en su momento y pueden revisarse a medida que avanza el análisis.