Durante la semana pasada (2026/07/20 - 2026/07/26), se destacan los siguientes 8 incidentes de seguridad notables, con pérdidas totales aproximadas de $39.5M.
| Fecha | Incidente | Tipo | Pérdida estimada |
|---|---|---|---|
| 2026/07/19* | Allbridge | Validación de entrada defectuosa | ~$1.65M |
| 2026/07/20 | Zilliqa | Generación de nonce defectuosa | ~$400K |
| 2026/07/20 | Wanchain | Codificación de mensaje defectuosa | ~$500K |
| 2026/07/22 | 42DAO | Compromiso de clave privada | ~$900K |
| 2026/07/22 | AFX Trade | Compromiso de clave privada | ~$24.15M |
| 2026/07/22 | B² Network | Compromiso de clave privada | ~$3.8M |
| 2026/07/23 | Verus | Compromiso de clave privada | ~$7.6M |
| 2026/07/24 | Lien Finance | Lógica de validación defectuosa | ~$542K |
*El incidente de Allbridge ocurrió el 19 de julio (17:51 UTC) y no fue cubierto en el informe de la semana pasada. Se incluye aquí por completitud.
- Zilliqa fue seleccionada porque expuso una vulnerabilidad no detectada durante mucho tiempo en una implementación de billetera fuera de la cadena que había puesto en riesgo de compromiso de clave privada a los usuarios de cuentas ZIL nativas heredadas desde 2019.
- Allbridge fue seleccionada porque demuestra una vulnerabilidad de alias de cuenta habilitada por el modelo de cuenta posicional de Solana. La vulnerabilidad tuvo que ser reconstruida a partir del binario del programa desplegado porque el código fuente no estaba disponible.
- Wanchain fue seleccionada porque demuestra cómo un puente puede ser vaciado sin comprometer la criptografía. Las firmas eran válidas y verificadas correctamente, sin embargo, una codificación de mensaje no inyectiva y sin delimitadores permitió que una autorización de 3,097.56 tokens fuera reinterpretada en Cardano como 203,001,692.164714 tokens.
- Lien Finance fue seleccionada porque ilustra cómo se puede eludir una validación que verifica que los balances coincidan con los recuentos sin comprobar la identidad y multiplicidad de los elementos coincidentes.
El mejor auditor de seguridad para Web3
Valida el diseño, el código y la lógica de negocio antes del lanzamiento
Destacado de la semana: Allbridge Core
Este incidente se destaca porque el patrón de alias de cuenta se aplica a cualquier programa de Solana que acepte la misma cuenta mutable en dos roles de instrucción. El mecanismo subyacente, dos vistas mutables del mismo estado donde la segunda escritura sobrescribe silenciosamente a la primera, tiene la misma estructura que el clásico bug de auto-transferencia de ERC-20 (transferFrom donde from == to). El post-mortem del proyecto [1] proporciona un resumen de alto nivel pero no detalla el mecanismo a nivel de código. El análisis a continuación fue reconstruido a partir del binario del programa desplegado para llenar ese vacío.
El 19 de julio de 2026 (17:51 UTC), Allbridge Core, un protocolo de puente entre cadenas en Solana, fue explotado por aproximadamente $1.65M (~$1.12M en USDC y ~$539K en USDT). La causa raíz fue que la instrucción de intercambio aceptaba la misma cuenta Pool en los roles de envío y recepción sin imponer que las dos sean diferentes. Cuando se pasó la misma cuenta dos veces, las actualizaciones de contabilidad interna de un rol fueron sobrescritas silenciosamente por el otro, mientras que las transferencias de tokens reales ya se habían liquidado. Cinco auto-intercambios distorsionaron suficientemente el estado registrado del Pool para que el atacante pudiera convertir una pequeña entrada de USDT en ~$2.24M de USDC.
Contexto
Allbridge Core es un puente entre cadenas que enruta intercambios a través de una unidad de contabilidad interna llamada vUSD. Un intercambio consta de dos tramos contables:
token fuente -- swap_to_v_usd(send_pool) --> vUSD
vUSD -- swap_from_v_usd(receive_pool) --> token destino
vUSD no es un token SPL. Existe únicamente como un campo dentro de los datos en cadena de cada Pool. Cada Pool rastrea token_balance, v_usd_balance y reserves. El tramo de envío transfiere el token fuente del usuario al vault del puente de envío, aumenta la contabilidad del lado del token del Pool de envío y calcula la salida de vUSD. El tramo de recepción añade ese vUSD al Pool de recepción, disminuye su contabilidad del lado del token y transfiere el token de destino desde el vault del puente de recepción al usuario.
Los programas de Solana reciben sus cuentas como un arreglo posicional del llamador. El manejador de instrucciones del programa especifica qué posiciones del arreglo corresponden a qué roles (send_pool, receive_pool, send_mint, etc.), pero el entorno de ejecución no impide que un llamador pase la misma dirección de cuenta en dos posiciones. Cuando el programa deserializa cuentas en dos posiciones diferentes, cada deserialización produce un objeto local separado en memoria. Las mutaciones a un objeto no afectan al otro. Si se pasa la misma cuenta dos veces, ambos objetos comienzan desde los mismos datos en cadena, pero divergen tan pronto como alguno es mutado.
Cuando el manejador termina, la lógica de salida del programa (por ejemplo, AccountsExit de Anchor) serializa cada objeto de cuenta de vuelta a los datos de la cuenta en un orden fijo. Si dos objetos mapean a la misma cuenta, la segunda serialización sobrescribe completamente a la primera.
Análisis de vulnerabilidad
El código fuente exacto del despliegue afectado de Allbridge Core no estaba disponible. El análisis fue reconstruido a partir del ELF de 1,770,736 bytes almacenado en ProgramData (SHA-256: 40f776...346bb6). El último slot de despliegue (204,727,029) es anterior al slot del ataque (433,941,722).
El programa con el bug es BrdgN2...ceWB. En las instrucciones Swap de la transacción del ataque (instrucciones 3 a 7), los cuatro pares de cuentas de envío y recepción eran idénticos:
| Roles | Cuenta |
|---|---|
send_mint / receive_mint |
Es9vMF...wNYB |
send_pool / receive_pool |
DW4a2E...wCX |
send_bridge_token / receive_bridge_token |
2xY9TD...vohV |
send_user_token / receive_user_token |
817UdW...CVct |
El analizador contiene 16 comparaciones recuperadas de 32 bytes que vinculan cada rol a su mint, vault, propietario, PDA, autoridad o programa Token esperados. Sin embargo, ninguna comparación impone send_pool.key() != receive_pool.key(). Los dos roles Pool se deserializan independientemente antes de que se ejecute el manejador.
El ELF desplegado muestra dos construcciones Pool, los cálculos de envío y recepción, y dos salidas Pool en un orden fijo:
L54249-L54252 function_9881(accounts[5]) -> send_pool local
L54294-L54297 function_9881(accounts[6]) -> receive_pool local
L74177-L74195 function_12623(send_pool) -> swap_to_v_usd
L74368-L74389 function_12940(receive_pool) -> swap_from_v_usd
L55932-L55936 function_8300(send_pool) -> serialización completa del Pool
L55940-L55944 function_8300(receive_pool) -> serialización completa del Pool
Cada llamada a function_9881 asigna un objeto Pool local separado de 176 bytes y copia los campos decodificados en él. Cuando las claves de cuenta son iguales, ambos objetos comienzan desde los mismos datos, pero una mutación del objeto de envío no cambia el objeto de recepción.
Pseudo-Rust reconstruido (comportamiento desplegado, no código fuente recuperado) [2] [3]:
let mut send_pool = Account::<Pool>::try_from(&accounts[5])?;
let mut receive_pool = Account::<Pool>::try_from(&accounts[6])?;
require_keys_eq!(send_pool.mint, send_mint.key());
require_keys_eq!(receive_pool.mint, receive_mint.key());
// Se verifican los vínculos de Vault, propietario, PDA, autoridad y programa Token.
// FALTANTE: require_keys_neq!(send_pool.key(), receive_pool.key());
token::transfer(send_user_token, send_bridge_token, amount)?;
let v_usd = swap_to_v_usd(&mut send_pool, amount)?;
let receive_amount =
swap_from_v_usd(&mut receive_pool, v_usd, receive_amount_min)?;
token::transfer(receive_bridge_token, receive_user_token, receive_amount)?;
// AccountsExit después de que el manejador retorna:
send_pool.exit(program_id)?; // primera escritura
receive_pool.exit(program_id)?; // segunda escritura, sobrescribe la primera
function_8300 comienza su escritor en la posición 0 y serializa los 131 bytes Pool definidos. La primera salida escribe send_pool; la segunda salida escribe receive_pool sobre los mismos bytes. El estado final de la cuenta es por lo tanto el valor obsoleto del lado de recepción, no una fusión de las dos actualizaciones locales.
Las transferencias de tokens SPL son CPIs separadas. Actualizan las cuentas del vault propiedad del programa SPL Token antes de que salgan los Pools, por lo que la segunda serialización del Pool no puede revertir la transferencia de entrada. Cada intercambio con alias deja por tanto los tokens transferidos en el vault mientras descarta la actualización del Pool del lado de envío correspondiente. La actualización del lado de recepción permanece y mueve el estado registrado del Pool hacia un mayor v_usd_balance y una menor contabilidad del lado del token.
El fallo central es que el programa no verifica que send_pool y receive_pool sean cuentas diferentes. Todas las demás validaciones (vinculación de mint, propiedad del vault, derivación del PDA) pasan porque ambos roles pertenecen legítimamente al mismo Pool.
Análisis del ataque
El siguiente análisis está basado en la transacción 3LNLaG...Y39Q.
El exploit se completó en una transacción que contenía un préstamo flash de Kamino, siete instrucciones de Swap de Allbridge y un reembolso a Kamino.
- Paso 1: El atacante tomó prestado ~1.12M de
USDCde Kamino y lo intercambió a través de Allbridge por ~949K deUSDT(instrucciones 1-2). Este primer intercambio suministró elUSDTnecesario para los siguientes auto-intercambios y desplazó los estados de los Pool deUSDCyUSDT.

- Paso 2: El atacante envió cinco auto-intercambios de ~100K
USDTutilizando las cuentas idénticas de mint, Pool, vault y token de usuario tanto para envío como para recepción (instrucciones 3-7). Debido a que el Pool de recepción se serializaba al final en cada llamada, la contabilidad persistida registraba la reducción del lado de recepción pero no la adición del lado de envío. Las salidas observadas disminuían en cada llamada a medida que se acumulaba la distorsión:
| Auto-intercambio | Entrada | vUSD registrado |
Precio (vUSD/USDT) | Salida USDT |
|---|---|---|---|---|
| 1 | ~100K | ~162K | ~5.24 | ~47.8K |
| 2 | ~100K | ~256K | ~16.6 | ~25.4K |
| 3 | ~100K | ~471K | ~57.2 | ~12.5K |
| 4 | ~100K | ~918K | ~190 | ~5.73K |
| 5 | ~100K | ~1.82M | ~563 | ~2.36K |
Las cinco llamadas transfirieron ~500K de USDT al vault y devolvieron ~93.8K de USDT. El token_balance registrado del Pool cayó con cada llamada mientras el saldo real del vault crecía, ampliando la brecha que el siguiente paso explota.

- Paso 3: El atacante aportó solo ~3.99K de
USDT(instrucción 8). El Pool deUSDTdistorsionado produjo ~2.24M devUSD, que el Pool deUSDCconvirtió en ~2.24M deUSDC. La salida inflada devUSDfue posible porque el balance de tokens registrado del Pool estaba muy por debajo de su saldo real del vault después de cinco rondas de actualizaciones descartadas del lado de envío.

- Paso 4: El atacante reembolsó el principal del préstamo flash de Kamino de ~1.12M de
USDCy la comisión de ~11.2USDC(instrucción 9). Los balances finales del atacante fueron ~1.12M deUSDCy ~539K deUSDT.
Conclusión
La causa raíz de este incidente fue la falta de validación de que send_pool y receive_pool hacen referencia a cuentas diferentes. Un único Pool mutable fue deserializado en dos objetos de contabilidad locales, y la segunda serialización completa sobrescribió a la primera después de que el programa Token ya había liquidado las transferencias del vault. Esto separó la contabilidad registrada del Pool de su saldo real del vault. Los alias de cuenta, el flujo de control del ELF, el orden de serialización, los registros de transacciones y los saldos del vault respaldan el mismo mecanismo [1].
Para los programas de Solana, cualquier instrucción que acepte el mismo tipo de cuenta en dos o más roles mutables debe imponer unicidad (require_keys_neq!) o fusionar las mutaciones antes de la salida. El sistema de restricciones #[account] del framework Anchor no impone unicidad entre roles por defecto, por lo que esta verificación debe añadirse explícitamente. El patrón general, dos vistas mutables del mismo estado donde la segunda escritura sobrescribe silenciosamente a la primera, puede aparecer en cualquier lugar donde los programas gestionen su propio orden de serialización.
Referencias
- [1] Allbridge Core, Post-Mortem Técnico: Exploit de Swap en Pool de Solana
- [2] Allbridge Core JS SDK, Interfaz de cuenta de Swap en Solana (
bridge.json) - [3] Allbridge Core EVM Contracts, Referencia de contabilidad del Pool (
Pool.sol)
Comienza con Phalcon Explorer
Profundiza en las transacciones para actuar con inteligencia
Pruébalo gratis ahoraMás incidentes de esta semana
Billetera Ledger de Zilliqa
El 20 de julio de 2026, Zilliqa observó actividad en cadena consistente con la explotación activa de cuentas ZIL nativas heredadas, con pérdidas conocidas de aproximadamente $400K. La causa raíz fue un nonce sesgado en la ruta de firma EC-Schnorr de la aplicación Ledger de Zilliqa: el código de generación de nonce copió los 32 bytes incorrectos de un búfer de 40 bytes después de la reducción modular, fijando los 64 bits más significativos en cero. Con varias firmas públicas de la misma cuenta, un atacante podría recuperar la clave privada mediante técnicas basadas en retículas y vaciar la cuenta.
Contexto
Las transacciones nativas de Zilliqa utilizan firmas EC-Schnorr sobre secp256k1. Para cada firma, el firmante debe muestrear un nonce efímero fresco, de ancho completo e impredecible donde (el orden de la curva secp256k1). El flujo de firma produce un compromiso , un desafío , y una respuesta , donde es la clave privada. Una vez difundido, es público.
La relación lineal es el punto crítico. Una implementación correcta permanece segura porque cada es fresco y distribuido uniformemente. Si el nonce está sesgado o es demasiado pequeño, cada firma pública filtra información sobre la misma clave privada.
Análisis de vulnerabilidad
La ruta relevante de generación de nonce fue introducida en la aplicación Ledger de Zilliqa en el commit "Bug fixes based on Ledger team review". El flujo previsto era: generar 40 bytes de aleatoriedad, reducir módulo el orden de la curva , y usar el escalar resultante como . Generar un entero aleatorio amplio y reducirlo módulo el orden de la curva no es un problema en sí mismo. El fallo apareció durante la copia al búfer del nonce:
unsigned char nonce[size+8];
cx_rng(nonce, size+8);
cx_math_modm(nonce, size+8, (unsigned WIDE char *) PIC(domain->n), size);
os_memcpy(T->K, nonce, size);
Después de cx_math_modm, el resultado escalar de 32 bytes se encuentra alineado a la derecha dentro del búfer de 40 bytes. os_memcpy(T->K, nonce, size) copia los primeros size (32) bytes, lo que retiene los 8 bytes de relleno con ceros iniciales y descarta los últimos 8 bytes de entropía. El nonce generado satisface por tanto en lugar de .
Esto significa que 64 bits de cada nonce están fijos en cero. De la ecuación de respuesta de Schnorr , cada firma proporciona una relación lineal modular pública donde el resultado está acotado por . Esta es una instancia del Problema del Número Oculto (HNP). Con aproximadamente cuatro o más firmas afectadas de la misma clave, un algoritmo estándar de reducción de retículas recupera la clave privada en segundos en hardware de consumo [1].
El defecto estuvo presente en todas las versiones publicadas de la aplicación Ledger de Zilliqa desde 2019 hasta que el incidente fue identificado en julio de 2026 [2].
No se proporciona Análisis de Ataque para este incidente. La explotación se realizó completamente fuera de la cadena: el atacante recuperó claves privadas a partir de firmas en cadena disponibles públicamente usando reducción de retículas, y luego firmó transacciones de transferencia estándar. No hay una secuencia de ataque en cadena de múltiples pasos para analizar.
Conclusión
Este incidente fue causado por un fallo en una implementación de firma fuera de la cadena, no por un bug en un contrato inteligente en cadena. La aplicación Ledger de Zilliqa producía firmas EC-Schnorr con nonces restringidos a , filtrando suficiente información estructurada para la recuperación de la clave privada después de varias transacciones nativas desde la misma cuenta.
La mitigación principal es la jubilación de claves, no simplemente actualizar la aplicación Ledger. Una aplicación corregida evita nuevas firmas debilitadas, pero no puede borrar las firmas ya públicas registradas en cadena. Cualquier clave afectada que haya producido suficientes firmas vulnerables debe tratarse como comprometida [2].
Para los equipos de billeteras y protocolos: traten la generación y codificación de nonces como código crítico para la seguridad, prefieran la generación determinista de nonces siguiendo un estándar bien revisado cuando sea aplicable, y para los usuarios afectados, proporcionen una ruta de migración coordinada que tenga en cuenta el front-running por parte de atacantes que ya podrían poseer la misma clave privada.
Referencias
- [1] Boneh y Venkatesan, Dificultad de calcular los bits más significativos de claves secretas (HNP)
- [2] Centro de incidentes Ledger de Zilliqa
Puente Cardano de Wanchain
El 20 de julio de 2026, el puente Cardano de Wanchain perdió aproximadamente 515.2M de NIGHT (~$500K) debido a una codificación de mensajes no inyectiva en el validador Plutus TreasuryCheck de Cardano [1]. El post-mortem del proyecto [2] describe el mecanismo de alto nivel pero no proporciona detalles de codificación a nivel de bytes ni código; el análisis a continuación reconstruye la colisión completa. Las firmas de los nodos del puente eran válidas y verificadas correctamente, pero la concatenación sin delimitadores de campos de longitud variable permitió al atacante desplazar los límites de bytes entre dos campos numéricos adyacentes, reinterpretando una autorización de 3,097.56 NIGHT como un retiro de 203,001,692.164714 NIGHT.
Contexto
El componente afectado es el sistema de tesorería entre cadenas de Cardano de Wanchain. Un usuario de la cadena fuente quema o bloquea tokens, los nodos del puente firman un mensaje de autorización construido a partir de los campos del redeemer analizados, y la prueba firmada se envía al validador Plutus TreasuryCheck de Cardano para liberar activos de la tesorería.
Análisis de vulnerabilidad
El validador TreasuryCheck verifica la firma del nodo del puente sobre una concatenación directa de 14 campos del redeemer analizados:
hashRedeemer = sha3_256 $ mconcat
[ toPkhPay, toPkhStk, policy, assetName
, packInteger amount, packInteger adaAmount
, txHash, packInteger index, packInteger mode
, uniqueId, packInteger txType, packInteger ttl
, packInteger outputCount, userData
]
La codificación entera utilizada por packInteger es de longitud variable, y la concatenación no tiene prefijo de longitud, separador de tipo ni serialización tipada con separación de dominio. Por lo tanto, diferentes tuplas semánticas pueden producir la misma cadena de bytes firmada.
En el ataque representativo, los nodos del puente firmaron un retiro normal:
amount = 3097560000 -> packInteger = b8a103c0
adaAmount = 1206800 -> packInteger = 126a10
combinado = b8a103c0126a10
El atacante envió un redeemer de Cardano analizado como:
amount = 203001692164714 -> packInteger = b8a103c0126a
adaAmount = 16 -> packInteger = 10
combinado = b8a103c0126a10
Las secuencias de bytes son idénticas. La verificación de firma pasó, y el contrato de Cardano interpretó el redeemer como un retiro aproximadamente 65,000 veces mayor que el autorizado.
Análisis del ataque
El siguiente análisis hace referencia a la transacción representativa de Cardano 0a4861...2ea1 y la transacción fuente de BSC 0xe90111...d5f26b.
-
Paso 1: El atacante creó solicitudes de puente de cadena fuente de apariencia normal. Para el caso representativo, la transacción BSC quemó 3,110
NIGHT, y la API de Wanchain registró una cantidad de recepción esperada en Cardano de 3,097.56NIGHT[3]. -
Paso 2: Los nodos del puente firmaron el hash de autorización construido a partir de los campos concatenados directamente.
-
Paso 3: El atacante cambió los límites del redeemer de Cardano entre
amountyadaAmountmanteniendo sin cambios la secuencia de bytes firmada. -
Paso 4: El validador Plutus
TreasuryCheckde Cardano analizó el redeemer como un retiro de alto valor y verificó la firma reutilizada con éxito. La transacción pagó 203,001,692.164714NIGHTal atacante. -
Paso 5: El mismo patrón se repitió en múltiples transacciones de Cardano, resultando en un drenaje total de aproximadamente 515,206,545.426856
NIGHT(~$500K).
Conclusión
No se rompió ninguna criptografía. Los firmantes produjeron firmas válidas, y el validador TreasuryCheck las verificó correctamente. El fallo estuvo en la construcción del mensaje: hashRedeemer plegó 14 campos de longitud variable en una cadena de bytes sin delimitador ni prefijo de longitud, haciendo la codificación no inyectiva. Diferentes tuplas de campos producen el mismo preimagen, el mismo hash y la misma firma válida.
La solución es hacer que la codificación firmada sea inyectiva para que exactamente una tupla de campos pueda producir cualquier cadena de bytes dada. La codificación de enteros de ancho fijo o el prefijo de longitud de cada campo fija los límites que el atacante movió. Un codificador estructurado estándar logra el mismo resultado y es más difícil de equivocar. La regla general: firmar una serialización canónica en lugar de una concatenación directa. Esta es la misma clase de bug que abi.encodePacked de Solidity con argumentos de longitud variable, donde diferentes tuplas de entrada pueden producir secuencias de bytes idénticas. Se aplica a cualquier mensaje firmado ensamblado a partir de campos de longitud variable, y es especialmente relevante para puentes donde las partes que construyen y analizan el mensaje operan en sistemas diferentes.
Referencias
- [1] Publicación de BlockSec Phalcon sobre el exploit de Wanchain
- [2] Post-Mortem del incidente del Puente Cardano-BNB Chain de Wanchain
- [3] Registro de la API de estado de Wanchain para la transacción BSC representativa
Lien Finance
El 24 de julio de 2026, Lien Finance, un protocolo OTC de bonos descentralizado en Ethereum, fue explotado por aproximadamente $542K en USDC. La causa raíz fue un fallo de validación en la función de intercambio de bonos: verificaba que el número total de bonos compartidos coincidiera entre los grupos de entrada y salida, pero no rastreaba qué bonos específicos se emparejaban. Esto permitió al atacante eludir el paso de quema para todos los bonos de entrada mientras acuñaba un bono sin colateral, que luego fue vendido por USDC real a través de los pools OTC del protocolo.
Contexto
Lien Finance es un protocolo DeFi que emite bonos colateralizados contra ETH. Su contrato BondMakerCollateralizedEth permite a los usuarios bloquear ETH como colateral para acuñar tokens de bono. El pago de un bono está definido por un fnMap, una función lineal por tramos que mapea el precio del colateral al vencimiento con la cantidad que paga ese bono.
La unidad significativa es el grupo de bonos. registerNewBondGroup() verifica que los pagos de todos los bonos en un grupo sumen al precio de ETH en cada punto de quiebre de su fnMap combinado. Un grupo que satisface esa condición reconstituye exactamente una unidad de colateral. Por eso los grupos son intercambiables: cualquier dos que satisfagan la condición valen lo mismo, y la ruta de conversión exchangeEquivalentBonds() se basa en esa garantía.
El registro de bonos y grupos es sin permiso: cualquier dirección puede registrar un nuevo bono suministrando un vencimiento y un fnMap arbitrario, y cualquier dirección puede registrar cualquier lista de IDs de bonos ya registrados como grupo, sujeto únicamente a la verificación de suma de pagos.
Análisis de vulnerabilidad
Los contratos con el bug son 0xDA6F...BEf0 y 0x8432...7de0.
Cuando un bono aparece tanto en el grupo de entrada como en el de salida, quemarlo y volver a acuñarlo inmediatamente es trabajo innecesario. El parámetro exceptionBonds nombra esos bonos para que el intercambio pueda omitir ambos pasos. La función impone esto con un único contador exceptionCount: se incrementa una vez por coincidencia al escanear el grupo de entrada y se decrementa una vez por coincidencia al escanear el grupo de salida, sin registrar jamás qué bondID coincidió.

Esto significa que el grupo de entrada [BondA, BondB] y el grupo de salida [BondC, BondA, BondA], con exceptionBonds = [BondA, BondB], pasan la validación. El contador alcanza 2 en el lado de entrada (una coincidencia cada uno para BondA y BondB) y regresa a 0 en el lado de salida, pero ambos decrementos provienen de las dos entradas de BondA. El intercambio no quema nada y solo acuña BondC: tokens del grupo de salida creados sin consumir ninguna entrada.
Análisis del ataque
El ataque está dividido en dos transacciones: el Paso 1 ocurre en 0xe8689a...284d0f, y los Pasos 2-5 ocurren en 0xb96d57...48e0e7.
-
Paso 1: El atacante llamó al
registerNewBond()sin permiso tres veces para definir BondA, BondB y BondC, compartiendo el mismo vencimiento. BondA y BondB son llamadas apalancadas que dividen el colateral uniformemente por debajo de $3,200, donde los pagos suman al precio deETHcomo requiere_assertBondGroup(). BondC es un LBT muy fuera del dinero con precio de ejercicio en $3,200 frente a un precio al contado de $1,870: cero valor intrínseco, pero aun así aproximadamente $7.64/IMT de valor temporal como opción. -
Paso 2: El atacante registró dos grupos de bonos.
registerNewBondGroup([BondA, BondB])devolvió el Grupo 36 (entrada), yregisterNewBondGroup([BondC, BondA, BondA])devolvió el Grupo 37 (salida). El pago plano-cero de BondC no añade nada a las sumas de los puntos de quiebre, por lo que su presencia no perturba la condición de equivalencia. BondA aparece dos veces en el Grupo 37, lo que es lo que hace que el intercambio pase la validación. -
Paso 3: El atacante llamó a
exchangeEquivalentBonds(inputGroup = 36, outputGroup = 37, amount = 6968565865905, exceptionBonds = [BondA, BondB]). Al escanear el grupo de entrada: BondA y BondB coinciden cada uno con una excepción, por lo que ambos omiten la quema yexceptionCountllega a 2. Al escanear el grupo de salida: BondC no coincide con nada y se acuña, luego las dos entradas de BondA coinciden cada una con una excepción y decrementan el contador a 0. -
Paso 4: El atacante vendió el BondC acuñado por
USDCa través de los pools OTC del protocolo (GeneralizedDotc), obteniendo 532,144USDC. -
Paso 5: El atacante repitió el mismo patrón contra el segundo contrato
BondMakerCollateralizedEth, llevando el beneficio total a aproximadamente 542,144.63USDC.

Conclusión
El mecanismo exceptionBonds, destinado a omitir la re-acuñación de bonos compartidos, podía aplicarse a todos los bonos de entrada a la vez duplicando un bondID en el grupo de salida. Esto rompió el invariante de que los tokens de bono solo se crean contra colateral depositado a través de issueNewBonds(). La solución es rastrear qué bondIDs específicos han sido emparejados (por ejemplo, usando un bitmap o un conjunto), asegurando que cada excepción se consuma exactamente una vez en ambos grupos.
Acerca de BlockSec
BlockSec es un proveedor integral de seguridad blockchain y cumplimiento cripto. Desarrollamos productos y servicios que ayudan a los clientes a realizar auditorías de código (incluyendo contratos inteligentes, blockchain y billeteras), interceptar ataques en tiempo real, analizar incidentes, rastrear fondos ilícitos y cumplir con las obligaciones AML/CFT, a lo largo de todo el ciclo de vida de protocolos y plataformas.
BlockSec ha publicado múltiples artículos de seguridad blockchain en conferencias de prestigio, ha reportado varios ataques de día cero en aplicaciones DeFi, ha bloqueado múltiples hackeos para rescatar más de 20 millones de dólares, y ha asegurado miles de millones en criptomonedas.
-
Sitio web oficial: https://blocksec.com/
-
Cuenta oficial de Twitter: https://twitter.com/BlockSecTeam



