Back to Blog

~US$ 10,26M: Ataques ao Term Finance e à MAYAChain | BlockSec

August 26, 2026
13 min read
Key Insights
  • 2 incidentes de segurança notáveis são destacados esta semana, envolvendo aproximadamente $10,26M em perdas totais. O incidente destacado, Term Finance (~$8,5M), foi uma tomada de governança na Ethereum, enquanto o MAYAChain (~$1,76M) foi uma falha encadeada de contabilidade e validação de estado em uma rede cross-chain baseada em Cosmos-SDK.

  • A governança por vault da Term Finance dependia de limites relativos de apoio e participação, sem um piso absoluto de capital ou abrangência; como quase ninguém havia convertido suas cotas de vault no token de governança, a participação era muito baixa para que qualquer voto contrário derrotasse uma proposta. Um atacante adquiriu uma supermaioria do poder de voto de um vault por cerca de 0,5 ETH, e o atraso de execução apenas postergou o resultado, sem nenhum guardião para intervir. Seis dos vaults da Term foram tomados dessa forma, totalizando cerca de $8,5M.

  • Na MAYAChain, um único depósito manipulado corrompeu a contabilidade interna da chain, fazendo com que retiradas válidas fossem tratadas como falhas, acionando um caminho de recuperação que inflacionou o saldo registrado do token nativo de um pool de baixa liquidez sem nenhum suporte real; o atacante então drenou esse valor inflacionado adicionando e retirando liquidez.

Durante a semana passada (2026/08/17 - 2026/08/23), destacam-se os seguintes 2 incidentes de segurança notáveis, envolvendo aproximadamente $10,26M em perdas totais.

Data Incidente Tipo Perda Estimada
2026/08/18 MAYAChain Falha na Lógica de Negócio ~$1,76M
2026/08/23 Term Finance Design de Governança Falho ~$8,5M

Motivos da seleção

  • MAYAChain: Selecionado porque uma falha encadeada de contabilidade e validação de estado permitiu que um único depósito manipulado corrompesse a reconciliação de saídas e inflasse o saldo registrado de um pool de baixa liquidez sem qualquer respaldo real, mostrando como diversos defeitos de baixo nível, cada um limitado por si só, podem se combinar para drenar uma rede de liquidez cross-chain.
  • Term Finance: Selecionado porque a participação quase nula na governança não deixou eleitorado suficiente para rejeitar uma proposta maliciosa, e não havia guardião ou caminho de cancelamento que sustentasse o atraso de execução, permitindo que um atacante com capital mínimo dominasse os votos, aprovasse uma proposta e drenasse aproximadamente $8,5M de seis cofres do protocolo.

Best Security Auditor for Web3

Validate design, code, and business logic before launch

Destaque da Semana: Term Finance

A Term Finance é destaque desta semana porque a falha não foi um bug de código, mas uma falha de design de governança, uma classe de risco que cresce à medida que os protocolos lançam uma DAO on-chain separada para cada cofre: quando quase ninguém participa, o controle do voto pode ser comprado a baixo custo, e a própria governança do cofre se torna a superfície de ataque.

Em 23 de agosto de 2026, a Term Finance, um protocolo de empréstimo de taxa fixa na Ethereum, perdeu aproximadamente $8,5M quando um atacante assumiu o controle da governança on-chain de seus cofres. Como praticamente nenhum depositante jamais havia mintado o token de governança de um cofre, o atacante conseguiu adquirir uma supermaioria do poder de voto por cerca de 0,5 ETH, superar as verificações de limiar de suporte e participação mínima do protocolo, e executar uma proposta maliciosa que revogou as estratégias do cofre e transferiu seus ativos; seis cofres da Term foram drenados dessa forma [1].

Contexto

A Term Finance é um protocolo de empréstimo de taxa fixa na Ethereum. Os Term Vaults são um produto separado construído sobre ele: cada cofre é um cofre ERC-4626 construído sobre o código do Yearn V3, onde um meta cofre aceita um único ativo e o alocado entre um conjunto de cofres de estratégia. O framework da Term implanta uma DAO Aragon OSx e um contrato TokenVoting junto com cada cofre, e essa DAO detém autoridade de upgrade e de papéis sobre o cofre com o qual é lançada. Um cofre, portanto, carrega sua própria superfície de governança desde a origem, independentemente de quem o cura ou se algum capital chega a ser alocado nele.

As decisões de governança são tomadas pelo TokenVoting. Cada cofre possui seu próprio token de cota e um wrapper de governança Aragon GovernanceWrappedERC20 correspondente; no ETH Meta Vault analisado, estes são tmvETH e gtmvETH. Qualquer detentor do token de cota pode chamar depositFor() para receber o token de governança na proporção um para um, e qualquer detentor do token de governança pode delegate() os votos resultantes. Quando uma proposta é criada, TokenVoting.createProposal() registra um snapshotBlock, um supportThreshold, e um minVotingPower derivado do fornecimento do token naquele snapshot; o poder de voto é então lido a partir do wrapper naquele bloco.

Análise da Vulnerabilidade

O contrato de governança no centro deste incidente é o TokenVoting. A execução da proposta é controlada apenas por _canExecute(), que, para uma proposta normal (não antecipada), exige que o voto não tenha sido executado ainda, que a proposta esteja encerrada, e que duas verificações sejam aprovadas: isSupportThresholdReached() e isMinParticipationReached().

Ambas as verificações são implementações corretas de votação por maioria, mas cada uma mede uma proporção relativa, em vez de um valor absoluto. isSupportThresholdReached() exige apenas que os votos favoráveis superem os votos contrários além da proporção configurada:

isMinParticipationReached() exige que os votos emitidos alcancem minVotingPower, que é, por sua vez, derivado como minParticipation * totalSupply no snapshot:

A causa raiz é que este design de governança não estabeleceu piso absoluto para o capital ou a amplitude necessários para mover uma proposta. Ambos os controles são puramente relativos aos votos emitidos e ao fornecimento do token, e como praticamente nenhum detentor de tmvETH jamais havia participado da governança ao encapsular em gtmvETH, esse fornecimento, e junto com ele o piso de participação mínima, permanecia próximo de zero. Uma maioria relativa de um eleitorado quase vazio foi, portanto, suficiente para superar ambas as verificações, e com tão poucos eleitores não havia ninguém para emitir os votos contrários que poderiam ter derrotado a proposta. Uma duração mínima de votação atrasou a execução, mas sem guardião ou caminho de cancelamento, o atraso apenas postergou a proposta aprovada, em vez de impedi-la.

Análise do Ataque

O atacante adquiriu uma participação de controle do token de governança de um cofre, e então a usou para aprovar e executar uma proposta que esvaziou aquele cofre; a mesma técnica foi aplicada em seis cofres da Term. A análise a seguir rastreia um cofre, com base nas transações 0xd354a1...d3014129 e 0x9f273f...44c2e8a0.

  • Passo 1: O atacante trocou 0,5 ETH por 0,485 tmvETH através de um forwarder da Mayan Finance, que roteou a troca e entregou as cotas do cofre ao atacante.

  • Passo 2: O atacante encapsulou os 0,485 tmvETH em 0,485 gtmvETH na proporção um para um, obtendo o poder de voto usado nos passos seguintes.

  • Passo 3: O atacante chamou propose(), que leu seu próprio poder de voto e invocou TokenVoting.createProposal(). O contrato registrou o snapshot, lendo o fornecimento total de governança naquele bloco como apenas 0,535 gtmvETH, então a participação do atacante era de cerca de 90,66% de todo o eleitorado.

  • Passo 4: O atacante chamou vote() para emitir todo o seu poder de voto a favor da proposta, tornando-se o único participante.

  • Passo 5: Após o período de votação, o atacante chamou executeProposal(). O controle canExecute() verificou isSupportThresholdReached(): com o atacante como único votante favorável, a proporção de suporte excedeu amplamente o limiar de 50%. Em seguida, verificou isMinParticipationReached(): o poder de voto do próprio atacante sozinho excedeu o minVotingPower. Ambas as verificações foram aprovadas.

  • Passo 6: A proposta maliciosa então foi executada, retirando os fundos que o cofre tmvETH havia alocado à sua estratégia, convertendo-os novamente em WETH líquido mantido pelo cofre, e permitindo que o atacante retirasse os ativos. Aplicado em seis cofres da Term, o ataque causou perdas totais de aproximadamente $8,5M.

Conclusão

Este incidente foi causado por um design de governança falho, e não por um erro de código: as verificações de suporte e participação eram puramente relativas, então, com um eleitorado quase vazio, uma maioria adquirida a baixo custo não enfrentou nenhum voto contrário, e o atraso de execução não tinha nenhum guardião ou caminho de cancelamento por trás. A governança que controla a custódia de ativos deve impor um quórum absoluto ou piso de participação, e sustentar qualquer atraso de execução com um guardião ou caminho de cancelamento e um feed de propostas monitorado, já que um timelock apenas posterga uma proposta aprovada, a menos que alguém possa agir durante esse período.


Mais Incidentes Nesta Semana

MAYAChain

Em 18 de agosto de 2026, a MAYAChain, uma rede de liquidez cross-chain baseada em Cosmos-SDK, perdeu aproximadamente $1,76M quando um único depósito manipulado corrompeu a contabilidade interna da chain. Ao fazer com que retiradas válidas parecessem ter falhado, o depósito acionou um caminho de recuperação que inflou o saldo registrado do token nativo de um pool de baixa liquidez sem qualquer respaldo real; o atacante então drenou esse valor inflado adicionando e retirando liquidez [2]. As saídas confirmadas on-chain foram de cerca de $1,36M, principalmente 20,83 BTC, e as reservas remanescentes em token nativo elevam a estimativa oficial para aproximadamente $1,76M.

Contexto

A MAYAChain é uma rede de liquidez cross-chain baseada em Cosmos-SDK cujo ativo nativo é CACAO. Eventos em chains externas são observados por validadores e reproduzidos na MAYAChain como transações observadas. Quando uma ação de entrada precisa enviar ativos para fora, o nó agenda um ou mais TxOutItems e, posteriormente, reconcilia as transações de saída observadas com esses registros agendados. Todos os ativos dos pools são custodiados juntos em Asgard vaults compartilhados; cada pool de liquidez é uma posição contábil sobre essa custódia compartilhada, e não um saldo segregado, e as retiradas são pagas a partir do Asgard com base nos saldos registrados do pool.

As trade accounts são posições contábeis nativas da MAYAChain para ativos de negociação como ARB~ETH e ARB~LINK. Uma retirada de trade account é iniciada através de uma MsgDeposit nativa com um memo como trade-:ARB~LINK. Embora o usuário assine uma única transação nativa, o handler de depósito reconstrói uma transação observada interna e armazena um ObservedTxVoter indexado pelo hash da transação nativa.

Análise da Vulnerabilidade

A causa raiz não foi um único bug isolado; foi uma cadeia de defeitos de contabilidade e validação de estado que se combinaram. A lógica problemática está nos handlers de depósito e de saída do MAYANode.

Primeiro, em handler_deposit.go, cada mensagem em uma transação nativa constrói um novo ObservedTxVoter indexado pelo hash da transação e o salva com 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)

Como cada mensagem em um lote compartilha o mesmo tx.ID, uma mensagem posterior sobrescreve o estado do voter escrito pelas mensagens anteriores, descartando seus metadados de agendamento de saída.

Em segundo lugar, em handler_common_outbound.go, a reconciliação de saída começa a partir de voter.OutboundHeight (ou voter.FinalisedHeight quando o primeiro é zero) e percorre para frente pelo período de assinatura:

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)
    ...
}

Quando o voter foi sobrescrito dessa forma, a busca começa na altura do depósito finalizado e nunca inspeciona o bloco que contém as saídas agendadas, então o handler as trata como ausentes e invoca o caminho de recuperação por punição.

Em terceiro lugar, em helpers.go, o caminho de recuperação valoriza o ativo "ausente" como um subsídio em CACAO, usando o valor observado bruto, sem qualquer limite em relação à profundidade real do ativo do pool:

f.stolenAsset = f.stolenAsset.Add(coin.Amount)
runeValue := pool.AssetValueInRune(coin.Amount)
f.subsidiseRune = f.subsidiseRune.Add(runeValue)

Em um pool com apenas cerca de 0,11 LINK de profundidade no lado do ativo, essa conversão sem limite poderia mapear um valor observado de LINK para um valor enorme em CACAO. A mesma rotina então grava o saldo inflado do pool no estado antes de tentar a transferência de financiamento:

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
}

Como a gravação do pool é confirmada antes da transferência, se a transferência não puder ser financiada, o BalanceCacao do pool nunca é revertido. Por fim, em handler_observed_txout.go, o chamador engole o erro retornado, marca o voter como concluído e continua, permitindo que um estado inconsistente do pool persista:

_, 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álise do Ataque

O atacante combinou esses defeitos em uma única transação nativa, e então extraiu valor do pool inflado. A análise a seguir baseia-se na transação da MAYAChain 516BA14D...E9B7.

  • Passo 1: O atacante enviou a transação nativa 516BA14D...E9B7 com 23 mensagens: 20 retiradas de trade-:ARB~ETH, 2 retiradas de trade-:ARB~LINK, e uma DONATE:ARB.LINK final de uma unidade base.

  • Passo 2: As retiradas de trade criaram saídas agendadas válidas, mas a mensagem final DONATE sobrescreveu o voter de entrada compartilhado para o mesmo hash de transação nativa, apagando o agendamento de saída das retiradas anteriores.

  • Passo 3: Quando as saídas de ARB.LINK foram observadas posteriormente, a busca de reconciliação usou os campos de altura do voter corrompido e nunca alcançou o bloco que continha as saídas agendadas, então as tratou como ausentes.

  • Passo 4: O caminho de recuperação por punição avaliou o LINK ausente em relação ao pool fino de ARB.LINK e inflou o lado CACAO do pool em cerca de 49,45M CACAO. A transferência de Reserve para Asgard que deveria ter financiado o subsídio falhou, já que a Reserve possuía apenas cerca de 168K CACAO, muito abaixo do subsídio inflado; mas o saldo do pool mutado persistiu e o handler falho foi marcado como concluído.

  • Passo 5: Em uma altura posterior, o atacante adicionou liquidez ao pool distorcido com 100 CACAO e uma pequena quantidade de LINK. Como um lado do pool havia sido levado a uma proporção muito fora do normal, a fórmula de adição de liquidez concedeu ao atacante cerca de 1T unidades de LP contra aproximadamente 731M unidades existentes.

  • Passo 6: O atacante retirou imediatamente quase toda a sua posição, recebendo aproximadamente 48,87M CACAO e 98,82 LINK pagos a partir dos Asgard vaults compartilhados contra o saldo inflado do pool, e trocou o CACAO extraído através dos pools da MAYAChain por BTC e outros ativos. O preço do CACAO caiu de cerca de $0,115 para uma mínima próxima de $0,013 durante a venda em massa.

Os 48,87M CACAO que o atacante retirou valiam nominalmente mais de $5M no preço anterior à exploração, mas nem tudo foi realizado como recebimentos externos: boa parte foi trocada novamente nos pools, derrubando o preço do CACAO, e arbitradores oportunistas não relacionados ao atacante também retiraram valor durante a queda. A extração diretamente confirmada on-chain foi de cerca de $1,36M, principalmente 20,83 BTC, e considerando os saldos remanescentes do atacante em CACAO e nas trade accounts, o valor chega à estimativa oficial de aproximadamente $1,76M.

Conclusão

Este incidente foi causado por uma cadeia de defeitos de contabilidade e validação de estado, e não por um único bug: nenhuma etapa foi catastrófica isoladamente, mas juntas permitiram que um depósito manipulado transformasse o saldo registrado de um pool fino em valor sem respaldo. O requisito fundamental é atomicidade e confiança limitada: uma mutação contábil e a transferência destinada a financiá-la devem ter sucesso ou falhar juntas, um caminho de recuperação falho nunca deve ser marcado como concluído, e as avaliações em relação a pools de baixa liquidez devem ser limitadas. O estado de entrada compartilhado também nunca deve ser sobrescrito silenciosamente por uma mensagem posterior na mesma transação.

Get Started with Phalcon Explorer

Dive into Transactions to Act Wisely

Try now for free

Get Started with Phalcon Security

Detect every threat, alert what matters, and block attacks.

Try now for free

Referências

Sobre a BlockSec

A BlockSec é uma provedora full-stack de segurança blockchain e conformidade cripto. Construímos produtos e serviços que ajudam os clientes a realizar auditoria de código (incluindo smart contracts, blockchain e wallets), interceptar ataques em tempo real, analisar incidentes, rastrear fundos ilícitos e cumprir obrigações de AML/CFT, ao longo de todo o ciclo de vida de protocolos e plataformas.

A BlockSec publicou diversos artigos de segurança em blockchain em conferências de prestígio, reportou vários ataques de dia zero em aplicações DeFi, bloqueou múltiplos ataques para resgatar mais de 20 milhões de dólares, e protegeu bilhões em criptomoedas.