Durante la semana pasada (2026/08/17 - 2026/08/23), se destacan los siguientes 2 incidentes de seguridad notables, que involucran aproximadamente $10.26M en pérdidas totales.
| Fecha | Incidente | Tipo | Pérdida Estimada |
|---|---|---|---|
| 2026/08/18 | MAYAChain | Falla de Lógica de Negocio | ~$1.76M |
| 2026/08/23 | Term Finance | Diseño de Gobernanza Defectuoso | ~$8.5M |
Razones de la selección
- MAYAChain: Seleccionado porque una falla encadenada de contabilidad y validación de estado permitió que un único depósito manipulado corrompiera la reconciliación de salidas y aumentara artificialmente el saldo registrado de un pool de baja liquidez sin respaldo real, mostrando cómo varios defectos de bajo nivel, limitados por sí solos, pueden combinarse para provocar el vaciado de una red de liquidez entre cadenas.
- Term Finance: Seleccionado porque una participación de gobernanza casi nula dejó sin electorado que pudiera rechazar una propuesta maliciosa, y no había ningún guardián ni vía de cancelación que respaldara el retraso de ejecución, permitiendo que un atacante con capital mínimo dominara los votos, aprobara una propuesta y vaciara aproximadamente $8.5M de seis bóvedas del protocolo.
El Mejor Auditor de Seguridad para Web3
Valide el diseño, el código y la lógica de negocio antes del lanzamiento
Destacado de la Semana: Term Finance
Term Finance es el destacado de esta semana porque la falla no fue un error de código, sino un defecto de diseño de gobernanza, una clase de riesgo que crece a medida que los protocolos lanzan una DAO en cadena separada con cada bóveda: cuando casi nadie participa, el control del voto puede comprarse a bajo costo, y la propia gobernanza de la bóveda se convierte en la superficie de ataque.
El 23 de agosto de 2026, Term Finance, un protocolo de préstamos de tasa fija en Ethereum, perdió aproximadamente $8.5M cuando un atacante tomó control de la gobernanza en cadena de sus bóvedas. Debido a que prácticamente ningún depositante había acuñado nunca el token de gobernanza de una bóveda, el atacante pudo adquirir una supermayoría de su poder de voto por alrededor de 0.5 ETH, superar las verificaciones de umbral de apoyo y participación mínima del protocolo, y ejecutar una propuesta maliciosa que revocó las estrategias de la bóveda y transfirió sus activos; seis de las bóvedas de Term fueron vaciadas de esta manera [1].
Contexto
Term Finance es un protocolo de préstamos de tasa fija en Ethereum. Los Term Vaults son un producto separado que se agrega sobre él: cada bóveda es una bóveda ERC-4626 construida sobre el código de Yearn V3, donde una meta bóveda acepta un único activo y lo asigna a un conjunto de bóvedas de estrategia. El framework de Term despliega una DAO de Aragon OSx y un contrato TokenVoting junto con cada bóveda, y esa DAO tiene autoridad de actualización y de roles sobre la bóveda con la que se lanza. Por lo tanto, una bóveda lleva consigo su propia superficie de gobernanza desde su creación, independientemente de quién la administre o si alguna vez se le asigna capital.
Las decisiones de gobernanza se toman mediante TokenVoting. Cada bóveda tiene su propio token de participación y un envoltorio de gobernanza GovernanceWrappedERC20 de Aragon correspondiente; en el ETH Meta Vault analizado, estos son tmvETH y gtmvETH. Cualquier titular del token de participación puede llamar a depositFor() para recibir el token de gobernanza uno a uno, y cualquier titular del token de gobernanza puede delegate() los votos resultantes. Cuando se crea una propuesta, TokenVoting.createProposal() registra un snapshotBlock, un supportThreshold, y un minVotingPower derivado del suministro de tokens en ese momento; el poder de voto se lee entonces desde el envoltorio en ese bloque.
Análisis de la Vulnerabilidad
El contrato de gobernanza en el centro de este incidente es TokenVoting. La ejecución de la propuesta está controlada únicamente por _canExecute(), que, para una propuesta normal (no anticipada), requiere que el voto no haya sido ya ejecutado, que la propuesta esté cerrada, y que pasen dos verificaciones: isSupportThresholdReached() y isMinParticipationReached().

Ambas verificaciones son implementaciones correctas de votación por mayoría, pero cada una mide una proporción relativa en lugar de una cantidad absoluta. isSupportThresholdReached() solo requiere que los votos afirmativos superen a los negativos por encima de la proporción configurada:

isMinParticipationReached() requiere que los votos emitidos alcancen minVotingPower, que a su vez se deriva como minParticipation * totalSupply en el momento del snapshot:

La causa raíz es que este diseño de gobernanza no estableció un piso absoluto sobre el capital o la amplitud necesaria para mover una propuesta. Ambas verificaciones son puramente relativas a los votos emitidos y al suministro de tokens, y debido a que casi ningún titular de tmvETH había participado alguna vez en la gobernanza mediante el envoltorio en gtmvETH, ese suministro, y con él el piso de participación mínima, se mantenía cerca de cero. Una mayoría relativa de un electorado casi vacío fue, por lo tanto, todo lo que se necesitó para superar ambas verificaciones, y con tan pocos votantes no había nadie que pudiera emitir los votos opuestos que podrían haber derrotado la propuesta. Una duración mínima de votación retrasó la ejecución, pero sin ningún guardián o vía de cancelación, el retraso solo postergó la propuesta en camino a aprobarse en lugar de impedirla.
Análisis del Ataque
El atacante adquirió una participación mayoritaria del token de gobernanza de una bóveda, y luego la usó para aprobar y ejecutar una propuesta que vació esa bóveda; la misma técnica se aplicó a seis de las bóvedas de Term. El siguiente análisis rastrea una bóveda, basado en las transacciones 0xd354a1...d3014129 y 0x9f273f...44c2e8a0.
-
Paso 1: El atacante intercambió
0.5 ETHpor0.485 tmvETHa través de un forwarder de Mayan Finance, que enrutó el intercambio y entregó las participaciones de la bóveda al atacante. -
Paso 2: El atacante envolvió los
0.485 tmvETHen0.485 gtmvETHuno a uno, obteniendo el poder de voto utilizado en los siguientes pasos. -
Paso 3: El atacante llamó a
propose(), que leyó su propio poder de voto e invocóTokenVoting.createProposal(). El contrato registró el snapshot, leyendo el suministro total de gobernanza en ese bloque como solo0.535 gtmvETH, por lo que la participación del atacante era aproximadamente el 90.66% de todo el electorado. -
Paso 4: El atacante llamó a
vote()para emitir todo su poder de voto a favor de la propuesta, convirtiéndose en el único participante. -
Paso 5: Después del período de votación, el atacante llamó a
executeProposal(). La verificacióncanExecute()comprobóisSupportThresholdReached(): con el atacante como único votante afirmativo, la proporción de apoyo superó ampliamente el umbral del 50%. Luego comprobóisMinParticipationReached(): el propio poder de voto del atacante por sí solo superóminVotingPower. Ambas verificaciones pasaron. -
Paso 6: La propuesta maliciosa se ejecutó entonces, retirando los fondos que la bóveda
tmvETHhabía asignado a su estrategia, convirtiéndolos de nuevo enWETHlíquido en poder de la bóveda, y permitiendo que el atacante retirara los activos. Aplicado a seis de las bóvedas de Term, el ataque causó aproximadamente $8.5M en pérdidas totales.
Conclusión
Este incidente fue causado por un diseño de gobernanza defectuoso en lugar de un error de código: las verificaciones de apoyo y participación eran puramente relativas, por lo que con un electorado casi vacío una mayoría adquirida a bajo costo no enfrentó ningún voto opositor, y el retraso de ejecución no tenía detrás ningún guardián o vía de cancelación. La gobernanza que controla la custodia de activos debería exigir un quórum absoluto o un piso de participación, y respaldar cualquier retraso de ejecución con un guardián o vía de cancelación y un feed de propuestas monitoreado, ya que un timelock solo posterga una propuesta en camino a aprobarse a menos que alguien pueda actuar durante ese período.
Más Incidentes Esta Semana
MAYAChain
El 18 de agosto de 2026, MAYAChain, una red de liquidez entre cadenas basada en Cosmos-SDK, perdió aproximadamente $1.76M cuando un único depósito manipulado corrompió la contabilidad interna de la cadena. Al hacer que retiros válidos parecieran haber fallado, el depósito activó una vía de recuperación que aumentó artificialmente el saldo registrado del token nativo de un pool de baja liquidez sin respaldo real; el atacante luego vació ese valor inflado agregando y retirando liquidez [2]. Las salidas confirmadas en cadena fueron de aproximadamente $1.36M, principalmente 20.83 BTC, y con las tenencias residuales del token nativo, la estimación oficial llega a aproximadamente $1.76M.
Contexto
MAYAChain es una red de liquidez entre cadenas basada en Cosmos-SDK cuyo activo nativo es CACAO. Los eventos en cadenas externas son observados por validadores y reproducidos en MAYAChain como transacciones observadas. Cuando una acción de entrada necesita enviar activos hacia afuera, el nodo programa uno o más TxOutItems y luego reconcilia las transacciones de salida observadas con esos registros programados. Todos los activos del pool se custodian juntos en bóvedas Asgard compartidas; cada pool de liquidez es una posición contable sobre esa custodia compartida en lugar de un saldo segregado, y los retiros se pagan desde Asgard según los saldos registrados de un pool.
Las cuentas de trading son posiciones contables nativas de MAYAChain para activos de trading como ARB~ETH y ARB~LINK. Un retiro de cuenta de trading se inicia mediante un MsgDeposit nativo con un memo como trade-:ARB~LINK. Aunque el usuario firma una única transacción nativa, el manejador de depósitos reconstruye una transacción observada interna y almacena un ObservedTxVoter indexado por el hash de la transacción nativa.
Análisis de la Vulnerabilidad
La causa raíz no fue un único error aislado; fue una cadena de defectos de contabilidad y validación de estado que se combinaron. La lógica defectuosa se encuentra en los manejadores de depósito y de salida de MAYANode.
Primero, en handler_deposit.go, cada mensaje en una transacción nativa construye un nuevo ObservedTxVoter indexado por el hash de la transacción y lo guarda con SetObservedTxInVoter():
txIn := ObservedTx{Tx: tx}
txInVoter := NewObservedTxVoter(txIn.Tx.ID, []ObservedTx{txIn})
txInVoter.Height = ctx.BlockHeight()
txInVoter.FinalisedHeight = ctx.BlockHeight()
txInVoter.Tx = txIn
h.mgr.Keeper().SetObservedTxInVoter(ctx, txInVoter)
Debido a que cada mensaje en un lote comparte el mismo tx.ID, un mensaje posterior sobrescribe el estado del voter escrito por los anteriores, descartando sus metadatos de programación de salidas.
Segundo, en handler_common_outbound.go, la reconciliación de salidas comienza desde voter.OutboundHeight (o voter.FinalisedHeight cuando el primero es cero) y escanea hacia adelante según el período de firma:
outHeight := voter.OutboundHeight
if outHeight == 0 {
outHeight = voter.FinalisedHeight
}
for height := outHeight; height <= ctx.BlockHeight(); height += signingTransPeriod {
txOut, err = h.mgr.Keeper().GetTxOut(ctx, height)
...
}
Cuando el voter ha sido sobrescrito de esta manera, el escaneo comienza en la altura de depósito finalizada y nunca inspecciona el bloque que contiene las salidas programadas, por lo que el manejador las trata como faltantes e invoca la vía de recuperación por penalización.
Tercero, en helpers.go, la vía de recuperación valora el activo "faltante" como un subsidio en CACAO usando el monto observado sin procesar, sin ningún límite frente a la profundidad real del activo del pool:
f.stolenAsset = f.stolenAsset.Add(coin.Amount)
runeValue := pool.AssetValueInRune(coin.Amount)
f.subsidiseRune = f.subsidiseRune.Add(runeValue)
En un pool con solo alrededor de 0.11 LINK de profundidad del lado del activo, esta conversión sin límite podía mapear un monto de LINK observado a un valor enorme en CACAO. La misma rutina luego confirma el saldo inflado del pool en el estado antes de intentar la transferencia de financiamiento:
pool.BalanceCacao = pool.BalanceCacao.Add(f.subsidiseRune)
pool.BalanceAsset = common.SafeSub(pool.BalanceAsset, f.stolenAsset)
if err = mgr.Keeper().SetPool(ctx, pool); err != nil {
...
}
runeToAsgard := common.NewCoin(common.BaseAsset(), f.subsidiseRune)
if err = mgr.Keeper().SendFromModuleToModule(ctx, ReserveName, AsgardName, common.NewCoins(runeToAsgard)); err != nil {
ctx.Logger().Error("fail to send subsidy from bond to asgard", "error", err)
return err
}
Debido a que la escritura del pool se confirma antes de la transferencia, si la transferencia no puede financiarse, el BalanceCacao del pool nunca se revierte. Finalmente, en handler_observed_txout.go, quien llama ignora el error devuelto, marca el voter como completado, y continúa, por lo que un estado de pool inconsistente puede persistir:
_, err = handler(ctx, m)
if err != nil {
ctx.Logger().Error("handler failed:", "error", err)
slashObservedOutbound("failed_outbound")
voter.SetDone()
h.mgr.Keeper().SetObservedTxOutVoter(ctx, voter)
continue
}
Análisis del Ataque
El atacante combinó estos defectos en una única transacción nativa, y luego extrajo valor del pool inflado. El siguiente análisis se basa en la transacción de MAYAChain 516BA14D...E9B7.
-
Paso 1: El atacante envió la transacción nativa
516BA14D...E9B7con 23 mensajes: 20 retirostrade-:ARB~ETH, 2 retirostrade-:ARB~LINK, y unDONATE:ARB.LINKfinal de una unidad base. -
Paso 2: Los retiros de trading crearon salidas programadas válidas, pero el mensaje final
DONATEsobrescribió el voter de entrada compartido para el mismo hash de transacción nativa, borrando la programación de salidas de los retiros anteriores. -
Paso 3: Cuando las salidas de
ARB.LINKfueron observadas posteriormente, el escaneo de reconciliación utilizó los campos de altura del voter alterado y nunca alcanzó el bloque que contenía las salidas programadas, por lo que las trató como faltantes. -
Paso 4: La vía de recuperación por penalización valoró el LINK faltante frente al pool delgado de
ARB.LINKe infló el lado enCACAOdel pool en aproximadamente49.45M CACAO. La transferencia de Reserve a Asgard que debía financiar el subsidio falló, ya que la Reserve solo tenía alrededor de168K CACAO, muy por debajo del subsidio inflado; pero el saldo del pool mutado persistió y el manejador fallido se marcó como completado. -
Paso 5: En una altura posterior, el atacante agregó liquidez al pool distorsionado con
100 CACAOy una pequeña cantidad de LINK. Debido a que un lado del pool había sido llevado muy fuera de proporción, la fórmula de adición de liquidez otorgó al atacante alrededor de1Tunidades de LP frente a aproximadamente731Munidades existentes. -
Paso 6: El atacante retiró de inmediato casi la totalidad de su posición, recibiendo aproximadamente
48.87M CACAOy98.82 LINKpagados desde las bóvedas Asgard compartidas contra el saldo inflado del pool, e intercambió elCACAOextraído a través de los pools de MAYAChain por BTC y otros activos. El precio deCACAOcayó de alrededor de$0.115a un mínimo cercano a$0.013durante la venta masiva.
Los 48.87M CACAO que el atacante retiró tenían un valor nominal de más de $5M al precio previo al exploit, pero no todo se materializó como ingresos externos: gran parte se volvió a intercambiar en los pools, colapsando el precio de CACAO, y arbitrajistas oportunistas no relacionados con el atacante también extrajeron valor durante el colapso. La extracción directamente confirmada en cadena fue de aproximadamente $1.36M, principalmente 20.83 BTC, y al contar el CACAO residual del atacante y los saldos de cuentas de trading, la cifra llega a la estimación oficial de aproximadamente $1.76M.
Conclusión
Este incidente fue causado por una cadena de defectos de contabilidad y validación de estado en lugar de un único error: ningún paso fue catastrófico por sí solo, pero juntos permitieron que un depósito manipulado convirtiera el saldo registrado de un pool delgado en valor sin respaldo. El requisito fundamental es la atomicidad y la confianza acotada: una mutación contable y la transferencia destinada a financiarla deben tener éxito o fallar juntas, una vía de recuperación fallida nunca debe marcarse como completada, y las valoraciones frente a pools de baja liquidez deben tener un límite. El estado de entrada compartido tampoco debería ser sobrescrito silenciosamente por un mensaje posterior en la misma transacción.
Comience con Phalcon Explorer
Sumérjase en las Transacciones para Actuar con Sabiduría
Pruébelo ahora gratisReferencias
- [1] RockawayX - Post Mortem: Incidente de Gobernanza de Term Finance
- [2] Análisis del incidente de MAYAChain
Sobre 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 de AML/CFT, a lo largo de todo el ciclo de vida de los 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 ataques 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



