Back to Blog

Resumo Semanal de Incidentes de Segurança Web3 | 9 a 15 de Mar de 2026

March 18, 2026
25 min read

Durante a semana passada (2026/03/09 - 2026/03/15), a BlockSec detectou e analisou oito incidentes de ataque, com perdas totais estimadas em ~$1,66M. A tabela abaixo resume esses incidentes, e análises detalhadas de cada caso são fornecidas nas subseções a seguir.

Data Incidente Tipo Perda Estimada
2026/03/09 Incidente EtherFreakers Lógica de negócio falha ~$25K
2026/03/10 Incidente Alkemi Lógica de negócio falha ~$89K
2026/03/10 Incidente MT Lógica de negócio falha ~$242K
2026/03/11 Incidente de Liquidação AAVE Configuração incorreta ~$1,01M
2026/03/11 Incidente Planet Finance Lógica de negócio falha ~$10K
2026/03/12 Incidente AM Lógica de negócio falha ~$131K
2026/03/12 Incidente DBXen Lógica de negócio falha ~$149K
2026/03/15 Incidente Goose Finance Lógica de negócio falha ~$8K

Incidente EtherFreakers

Resumo

Em 9 de março de 2026, o EtherFreakers, um jogo NFT na Ethereum, foi explorado devido a uma dupla contagem incorreta, resultando em uma perda de ~$25K. Cada NFT no jogo possui um saldo de ETH sacável (chamado de "energia"). Como mecânica do jogo, os jogadores podem usar attack() para fazer um NFT capturar outro e reivindicar a energia do alvo. No entanto, o contrato paga o saldo do alvo, mas transfere o NFT antes de liquidar sua contabilidade. Um hook de transferência então lê dados desatualizados anteriores ao pagamento e os alimenta parcialmente de volta em um pool global de dividendos, inflando o pool sem novo ETH como lastro. O explorador repetiu essa mecânica de captura para inflar o índice global e então drenou o saldo inflado de um lote de NFTs.

Contexto

EtherFreakers é um jogo NFT on-chain onde cada NFT (chamado de "Freaker") possui um saldo de ETH sacável chamado energy. O sistema funciona como um pool de dividendos: quando certas ações ocorrem, uma fração de ETH é distribuída proporcionalmente a todos os Freakers. O ETH reclamável de cada Freaker é rastreado por um acumulador global freakerIndex combinado com um peso de participação por token fortune.

Concretamente, a fórmula contábil é: energyOf = basic + (freakerIndex - index) * fortune. O freakerIndex aumenta quando _dissipateEnergyIntoPool(amount) é executado, distribuindo 80% do amount para todos os Freakers e 20% para os criadores. Depósitos diretos via charge() aumentam apenas o basic sem afetar o freakerIndex. Portanto, aumentos no freakerIndex devem sempre ser respaldados por Ether real entrando no sistema. Se o freakerIndex crescer sem o correspondente influxo de ETH, os Freakers podem resgatar mais Ether do que o contrato realmente possui.

Análise de Vulnerabilidade

A causa raiz é uma ordem de execução incorreta no contrato EtherFreak (0x3A27...c0f33). Quando uma captura é bem-sucedida, a função attack() executa estes passos em ordem:

  1. Linha 237: Paga targetCharge (a energia total do NFT alvo) ao defensor como uma transferência direta de ETH. A energia é agora gasta.
  2. Linha 240: Chama _transfer(defender, capturer, targetId) para mover o NFT. Internamente, _transfer() invoca o hook ERC-721 _beforeTokenTransfer(), que chama _dissipateEnergyIntoPool() com 0,1% de energyOf(targetId). Esta é a primeira chamada a _dissipateEnergyIntoPool(), e ela lê um valor desatualizado porque o passo 5 ainda não ocorreu.
  3. Linha 241: Chama _dissipateEnergyIntoPool(sourceSpent) explicitamente. Esta é a segunda chamada, que faz parte da lógica normal do jogo.
  4. Linhas 244-251: Atualiza energyBalances tanto para sourceId quanto para targetId.

O bug está no passo 2: como energyBalances[targetId] ainda não foi atualizado, o hook ainda vê o saldo anterior ao pagamento e alimenta parte da energia já gasta no pool de dividendos. O pagamento direto em ETH no passo 1 e a entrada no pool no passo 2 ambos consomem a mesma energia, inflando freakerIndex sem novo lastro em ETH.

freakerIndex cresce sempre que _dissipateEnergyIntoPool() é chamado:

Análise do Ataque

A análise a seguir é baseada na transação 0x89e24d...9abd2942.

  • Passo 1: Emprestar 1.700 WETH via flash loan.

  • Passo 2: Criar dois novos Freakers, token 590 e token 591, sob endereços controlados pelo explorador.

  • Passo 3: Chamar repetidamente a função attack(590, 591) do jogo e manter as execuções no ramo de captura bem-sucedida.

  • Passo 4: Após cada sucesso, transferir o token 591 de volta ao auxiliar para que o mesmo par possa ser reutilizado.

  • Passo 5: Cada iteração bem-sucedida infla o freakerIndex além do Ether realmente conservado pelo sistema.

  • Passo 6: Uma vez que o índice é suficientemente alto, descarregar um lote de Freakers previamente controlados. Os IDs de token 496 a 520 são cada um descarregado por 0,278052246002402082 Ether.

  • Passo 7: Converter o Ether drenado em WETH, pagar o flash loan de 1.700 WETH, e manter cerca de 7,498 WETH como lucro.

Conclusão

A causa raiz está no fluxo de captura bem-sucedida de attack(): o EtherFreakers paga targetCharge antes que o estado de energia do token alvo seja liquidado. _transfer() então aciona _beforeTokenTransfer(), que lê o energyOf(targetId) desatualizado anterior ao pagamento e dissipa parte dele no pool. Isso aumenta o freakerIndex sem novo lastro em Ether, fazendo com que a mesma energia do alvo seja contabilizada tanto como pagamento quanto como entrada no pool. Este é um bug de inflação de lógica de negócio, não um bug de reentrância.

Para reduzir riscos semelhantes no futuro:

  • Evite recomputar valores econômicos a partir de estado mutável dentro de hooks de transferência enquanto a mesma transação ainda está sendo liquidada.

  • Se o hook de transferência lê uma variável de estado, garanta que a ordem de execução não afete o resultado (por exemplo, liquide o estado antes que o hook seja executado, não depois).


Incidente Alkemi

Resumo

Em 10 de março de 2026, o protocolo Alkemi na Ethereum foi explorado, resultando em uma perda de ~$89K. A causa raiz é um erro contábil e uma lógica de negócio falha. A lógica de liquidação falha permite que qualquer pessoa liquide sua própria posição dentro da mesma transação e lucre com isso. Além disso, um erro contábil faz com que a dedução da própria garantia do atacante durante a liquidação seja sobrescrita, permitindo que o atacante obtenha recompensas de liquidação sem arcar com o custo pretendido.

Contexto

Alkemi é um protocolo de empréstimo. Quando a posição de um tomador de empréstimo fica subcapitalizada, qualquer pessoa pode chamar liquidateBorrow() para pagar parte da dívida e confiscar a garantia com desconto. Para evitar liquidações excessivas, o protocolo limita o valor máximo reembolsável por transação ao mínimo de três valores:

  1. O saldo de empréstimo atual do tomador (currentBorrowBalance_TargetUnderwaterAsset).
  2. O reembolso máximo que a garantia do tomador pode cobrir após aplicar o desconto de liquidação (calculateDiscountedBorrowDenominatedCollateral()).
  3. O valor de reembolso necessário para trazer a conta de volta ao limite de liquidação (calculateDiscountedRepayToEvenAmount()), verificado apenas quando o mercado isSupported.

Análise de Vulnerabilidade

A causa raiz é a lógica de negócio falha e um erro contábil no protocolo Alkemi (0x4822...a888). Como currentBorrowBalance_TargetUnderwaterAsset é necessariamente maior que 0 enquanto o tomador tiver uma dívida pendente, e o valor retornado por calculateDiscountedBorrowDenominatedCollateral() também é necessariamente maior que 0 enquanto o tomador tiver garantia, o protocolo AlkemiEarnPublic efetivamente depende de calculateDiscountedRepayToEvenAmount() para determinar se um dado empréstimo pode ser liquidado. Nessa função, o valor da dívida que precisa ser liquidada deve ser calculado com base em uma variável chamada accountShortfall_TargetUser.

No entanto, na implementação real, a função utiliza uma variável global closeFactorMantissa para calcular um limite superior sobre o valor da dívida que pode ser reembolsado, e retorna esse valor. Como resultado, um atacante consegue tomar emprestado e imediatamente liquidar sua própria posição dentro da mesma transação.

Além disso, na função liquidateBorrow(), quando o liquidante e o tomador são o mesmo endereço, as variáveis supplyBalance_TargetCollateralAsset e supplyBalance_LiquidatorCollateralAsset apontam para o mesmo slot de armazenamento. A função então calcula um "saldo reduzido" e um "saldo recompensado" separadamente com base no mesmo saldo inicial, e subsequentemente os grava de volta no mesmo slot de armazenamento em sequência. Como o saldo reduzido é gravado primeiro e o saldo recompensado é gravado depois, o efeito de redução é sobrescrito e perdido, deixando apenas o resultado recompensado. Isso permite que o atacante amplifique ainda mais seu lucro.

Análise do Ataque

A análise a seguir é baseada na transação 0xa170...6d9d.

  • Passo 1: O atacante obteve um flash loan de 51e18 WETH da Balancer.

  • Passo 2: O atacante converteu 51e18 WETH em ETH e forneceu ao protocolo Alkemi.

  • Passo 3: O atacante tomou emprestado 39,5e18 ETH da Alkemi.

  • Passo 4: O atacante liquidou sua própria posição usando 39,5395e18 ETH.

  • Passo 5: O atacante retirou 93,5e18 ETH da Alkemi.

  • Passo 6: O atacante reembolsou o flash loan e obteve um lucro de 43,4e18 ETH.

Conclusão

A causa raiz é que a lógica de liquidação falha permite que o atacante tome emprestado e então liquide sua própria posição dentro da mesma transação para obter lucro, enquanto a lógica contábil incorreta amplifica ainda mais os ganhos do atacante.

Para reduzir riscos semelhantes no futuro:

  • Para atualizações de saldo do tomador e do liquidante, o protocolo deve operar diretamente nas variáveis de armazenamento em vez de copiar os saldos para variáveis de memória temporárias para cálculo e gravação de volta separados.

Incidente MT

Resumo

Em 10 de março de 2026, o MT Token, um token deflacionário na BNB Chain, foi explorado, resultando em uma perda de ~$242K. A causa raiz é uma lógica de restrição de negociação falha combinada com tratamento inconsistente de condições especiais de transferência. Durante a fase de deflação, o contrato restringe operações de compra quando a reserva do pool excede um limite fixo. No entanto, o contrato trata transferências de valores exatos (por exemplo, 2e17 MT) como ações de vínculo de referência, permitindo que o atacante contorne a restrição de compra e adquira tokens iniciais. Além disso, a lógica de restrição depende de detecção de caminho incompleta (isBuy) e não cobre rotas de swap indiretas como Pair para Router, enquanto verificações de lista de permissões encurtam validações críticas. O atacante acumulou tokens MT sem acionar restrições ou taxas, manipulou pendingBurnAmount via operações controladas de liquidez e negociações, e forçou o pool a um estado anormal onde o preço do token foi artificialmente inflado.

Contexto

MT Token é um token deflacionário na BNB Chain com restrições de negociação integradas. Durante a fase de deflação, o contrato bloqueia operações de compra quando a reserva de MT no pool excede 21.000e18. Uma vez que a reserva caia abaixo desse limite, a fase de deflação termina e as compras são reativadas. MT Token também inclui um mecanismo de referência: uma transferência de exatamente 2e17 MT ou 1e17 MT é tratada como uma ação de vínculo de referência em vez de uma negociação normal.

Análise de Vulnerabilidade

A causa raiz foi um design de restrição de compra falho no contrato MT (0x037E...b449). Em condições normais, os atacantes deveriam ser incapazes de adquirir MT como capital inicial durante a fase restrita. No entanto, o contrato trata uma transferência de exatamente 2e17 MT como uma ação de vínculo de referência em vez de uma compra, o que permite que um atacante adquira 2e17 MT contornando a restrição de compra.

Além disso, a restrição de negociação depende do ramo isBuy para bloquear compras, mas não cobre o caminho "Pair para Router". Como tanto o Router quanto o Pair são endereços da lista de permissões, tais transferências encurtam na verificação da lista de permissões e nunca alcançam a lógica de restrição de compra, permitindo que um atacante adquira MT roteando compras para o Router e subsequentemente extraindo tokens de volta para si mesmo removendo liquidez.

Análise do Ataque

A análise a seguir é baseada na transação 0xfb57...fca6.

  • Passo 1: O atacante obteve um flash loan de ~358.681e18 WBNB.

  • Passo 2: O atacante comprou 2e17 MT, contornando assim a restrição de compra.

  • Passo 3: O atacante forneceu 4e12 WBNB e 2e17 MT ao Pair para adicionar liquidez. Essa transferência contornou a lógica de cobrança de taxas pelo mesmo motivo acima.

  • Passo 4: O atacante comprou ~10.000.000e18 tokens MT do Pair para o Router, contornando assim tanto a restrição de compra quanto a lógica de cobrança de taxas.

  • Passo 5: O atacante removeu metade de sua posição de liquidez, extraindo todos os tokens MT mantidos pelo Router no processo, e então vendeu o MT recuperado por WBNB. Neste passo, pendingBurnAmount foi manipulado para aproximadamente 9.000.000e18.

  • Passo 6: O atacante comprou ~10.000.000e18 tokens MT novamente, reduzindo a reserva de MT do pool para ~6.756.516e18, que era menor que pendingBurnAmount.

  • Passo 7: O atacante removeu a metade restante de sua posição de liquidez, retirou os tokens MT comprados e então chamou distributeDailyRewards() para queimar MT do pool. Como resultado, a reserva de MT foi reduzida para 21.000e18.

  • Passo 8: O atacante trocou todo o MT de volta por ~1.198e18 WBNB, reembolsou o flash loan e finalizou o lucro.

Conclusão

Este exploit foi causado por restrições de negociação incorretas, que permitiram ao atacante contornar a proibição de compra adquirindo exatamente BINDING_AMOUNT de tokens MT. Após obter tokens MT, o atacante conseguiu contornar tanto a lógica de cobrança de taxas quanto a restrição de compra adicionando liquidez primeiro, depois comprando MT para o Router, e finalmente removendo liquidez para recuperar os tokens. O atacante então acumulou pendingBurnAmount via operações de venda e executou a queima para empurrar as reservas do pool a um estado anormal, permitindo-lhe vender MT a um preço inflado artificialmente e lucrar.

Para reduzir riscos semelhantes no futuro:

  • Imponha separação estrita entre semântica de transferência e lógica de negociação.

Incidente de Liquidação AAVE

Resumo

Em 11 de março de 2026, a AAVE sofreu $21M em liquidações incorretas na Ethereum, resultando em uma perda de ~$1,01M. A causa raiz foi um preço oracle incorreto para wstETH, que fez com que posições originalmente saudáveis se tornassem subcapitalizadas. Como resultado, as posições dos usuários foram liquidadas, levando a perdas financeiras.

Contexto

A AAVE usa adaptadores de oracle para precificar ativos encapsulados como wstETH. O adaptador CAPO (Capped Price Oracle) deriva o preço do wstETH multiplicando o preço base de ETH/USD por uma taxa de conversão (getRatio(), ou seja, quanto ETH vale um wstETH). Para evitar manipulação da taxa, o CAPO aplica um limite de crescimento baseado em snapshot:

maxRatio = snapshotRatio + maxGrowthPerSecond x (currentTime - snapshotTimestamp)

e limita a saída de getRatio() durante a precificação (se currentRatio > maxRatio, usa maxRatio). Esse mecanismo limita efetivamente a deriva máxima ascendente da taxa e o preço resultante.

Análise de Vulnerabilidade

A causa raiz foi uma incompatibilidade tempo-taxa na configuração âncora do oracle CAPO (0xe1D9...61Ef): o timestamp do snapshot e a taxa do snapshot foram configurados, mas a taxa do snapshot foi configurada abaixo da taxa real de wstETH/ETH. Como resultado, o maxRatio calculado pelo adaptador ficou abaixo da taxa ao vivo e limitou getRatio() para baixo, subvalorizando sistematicamente o preço oracle de wstETH/USD. Essa desvalorização da garantia reduziu o fator de saúde das posições usando wstETH como garantia, fazendo com que contas originalmente saudáveis fossem incorretamente classificadas como não saudáveis e liquidadas.

Análise do Ataque

A análise a seguir é baseada na transação 0x9064...8a9c.

  • Passo 1: O liquidante obteve um flash loan de ~6304e18 WETH e liquidou o tomador.

  • Passo 2: O liquidante reembolsou o flash loan, completando a liquidação.

Conclusão

Essa liquidação foi causada por uma configuração incorreta do preço oracle, que empurrou incorretamente os tomadores que deveriam permanecer saudáveis para um estado não saudável, acionando assim a liquidação de suas posições.

Para reduzir riscos semelhantes no futuro:

  • Garanta que parâmetros críticos sejam verificados quanto à correção antes de cada atualização.

  • Adicione verificações de validação na implementação para rejeitar parâmetros incorretos e impedir que configurações incorretas sejam aplicadas com sucesso.


Incidente Planet Finance

Resumo

Em 11 de março de 2026, o Planet Finance foi explorado na BNB Chain, resultando em uma perda estimada de ~$10K. A causa raiz foi que o protocolo tratou erroneamente aumentos no saldo de empréstimo armazenado de um tomador como juros acumulados, permitindo que um atacante tomasse emprestado e acionasse a liquidação de descontos repetidamente para subestimar sua dívida registrada.

Contexto

Planet Finance é um protocolo de empréstimo que permite que os tomadores paguem com um desconto nos juros. O desconto é escalonado e determinado pela proporção entre o GAMMA apostado pelo usuário e seu valor apostado em outros ativos: quanto maior essa proporção, maior o desconto no reembolso. O cronograma de desconto compreende três níveis, variando de 0% (mínimo) a 50% (máximo).

Análise de Vulnerabilidade

A causa raiz foi que, ao liquidar o desconto de um tomador em changeUserBorrowDiscount(), o protocolo (0x4c9E...F467) tratou erroneamente o aumento no saldo de empréstimo armazenado do tomador como juros recém-acumulados. Como resultado, o desconto destinado a aplicar-se apenas a juros acumulados foi incorretamente aplicado ao principal recém-tomado emprestado, reduzindo indevidamente a dívida registrada do tomador. Um atacante poderia realizar repetidamente o ciclo borrow seguido de changeUserBorrowDiscount para acumular descontos excessivos, fazendo com que o passivo registrado on-chain fosse consistentemente menor do que o valor real emprestado, e eventualmente lucrar com a discrepância.

Análise do Ataque

A análise a seguir é baseada na transação 0x5f45...5ec9.

  • Passo 1: O atacante obteve um flash loan de 200.000e18 USDT.

  • Passo 2: O atacante usou 5.000e18 USDT para comprar WBNB, e então usou o WBNB adquirido para comprar ~8.726.524e18 GAMMA.

  • Passo 3: O atacante primeiro apostou todo o GAMMA adquirido no mercado gGAMMA, depois forneceu o USDT restante como garantia, o que aumentou seu desconto de reembolso para 5% e habilitou o empréstimo subsequente.

  • Passo 4: O atacante chamou repetidamente borrow e depois updateUserDiscount para reduzir continuamente sua dívida registrada.

  • Passo 5: O atacante finalmente reembolsou a dívida, resgatou a garantia e realizou o lucro.

Conclusão

Este incidente foi causado pela lógica de liquidação de desconto falha do Planet Finance em changeUserBorrowDiscount(), que erroneamente trata o aumento no saldo de empréstimo armazenado de um tomador como juros recém-acumulados e aplica o desconto de juros sobre esse delta. Um atacante pode repetidamente chamar borrow seguido de updateUserDiscount para subestimar sua dívida registrada e, em última análise, pagar menos do que o passivo real para extrair lucro.

Para reduzir riscos semelhantes no futuro:

  • Diferencie juros e novos empréstimos no protocolo de empréstimo.

Incidente AM

Resumo

Em 12 de março de 2026, o AM Token, um token deflacionário na BNB Chain, foi explorado com uma perda estimada de ~$131K. O AM Token implementa um mecanismo deflacionário onde cada venda aciona uma queima adicional do pool de liquidez, removendo permanentemente tokens para reduzir o fornecimento total. No entanto, a queima não é executada imediatamente - em vez disso, o valor total da venda é registrado como toBurnAmount e a queima real é adiada para a próxima venda. Esse atraso cria uma janela entre o registro e a execução, durante a qual um atacante pode recomprar AM para reduzir a reserva de AM do pool até toBurnAmount. Quando a próxima venda aciona a queima diferida, toda a reserva de AM é eliminada, elevando o preço a um nível extremo e permitindo que o atacante venda AM com lucro.

Contexto

AM Token é um token deflacionário na BNB Chain. Em cada venda, o contrato registra a quantidade de AM envolvida no swap como toBurnAmount, e queima essa quantidade registrada do pool de liquidez durante a próxima venda. Na prática, as vendas acionam uma queima atrasada que reduz a reserva de AM do pool. Além disso, antes de executar a queima, o protocolo converte o totalTokenFee acumulado em USDT e o distribui de acordo com sua lógica de alocação de taxas.

Análise de Vulnerabilidade

A causa raiz foi que a lógica de venda do token (0x27f9...213f) acumula o valor total de AM trocado como toBurnAmount e só executa a queima na próxima venda, removendo tokens do par AM/USDT e chamando pair.sync() para atualizar as reservas. Esse design permite que um atacante manipule as reservas de AM do pool, distorça o preço on-chain e lucre via arbitragem.

Análise do Ataque

A análise a seguir é baseada na transação 0xd0d1...f859.

  • Passo 1: O atacante obteve um flash loan de ~27.265.119e18 USDC e ~361.710e18 WBNB, e então os converteu em ~100.423.811e18 USDT.

  • Passo 2: O atacante trocou ~5.062e18 tokens AM por USDT, o que manipulou o toBurnAmount registrado no contrato para ~4.303e18.

  • Passo 3: O atacante trocou USDT por AM Token, reduzindo a reserva de AM do pool para ~4.303e18.

  • Passo 4: O atacante transferiu 6 wei de AM para o pool, acionando a lógica de queima do caminho de venda. Como resultado, o contrato queimou todo o saldo de AM do pool, reduzindo a reserva de AM para 0. Nota: o protocolo primeiro tenta converter as taxas acumuladas em USDT antes da queima. Esse caminho de conversão de taxas também aciona a lógica de queima do ramo de venda. Após a queima ser executada e a reserva de AM chegar a 0, o swap de taxas falha. Como está envolto em um try/catch, a falha não reverte a transação. Em vez disso, a execução continua e o acumulador de taxas é redefinido para 0.

  • Passo 5: O atacante chamou pool.sync() e transferiu o USDT restante e 1 wei de AM para o pool. Como ambos os tokens foram transferidos simultaneamente, o contrato tratou isso como addLiquidity, de modo que toBurnAmount não foi acumulado. A reserva de AM foi atualizada para 7.

  • Passo 6: O atacante trocou os tokens AM restantes por USDT. Durante esse swap, a transferência de AM para o Pair acionou a lógica de queima do caminho de venda, reduzindo a reserva de AM para 1. Além disso, como totalFeeAmount havia sido redefinido para 0 no Passo 4, a conversão de taxas para USDT não foi mais executada, permitindo que o atacante vendesse AM a um preço artificialmente inflado.

  • Passo 7: O atacante reembolsou o flash loan e realizou o lucro restante.

Conclusão

Este incidente foi causado pelo mecanismo de queima falho do AM Token, que acumula o AM envolvido no swap de cada venda como toBurnAmount e então queima essa quantidade do Par AM/USDT na próxima venda enquanto chama pool.sync(). Isso permite que um atacante manipule as reservas de AM do Par para um nível extremo e venda AM a um preço artificialmente inflado para drenar USDT.

Para reduzir riscos semelhantes no futuro:

  • Limite o valor máximo de queima por transação e limite a frequência com que a queima pode ser acionada, impedindo que atacantes consumam uma grande parte das reservas de tokens do pool em um curto período de tempo.

Incidente DBXen

Resumo

Em 12 de março de 2026, o DBXen, um protocolo burn-to-earn na Ethereum e BNB Chain, foi explorado com uma perda total de ~$149K. A causa raiz foi uma inconsistência entre _msgSender() e msg.sender. Quando burnBatch() é chamado pelo forwarder, a quantidade de XEN queimado é registrada sob _msgSender() (controlado pelo atacante), mas os registros de ciclo são atualizados em msg.sender (o forwarder). Essa divisão permite que o atacante reivindique recompensas e taxas contra registros de ciclo desatualizados, resultando em pagamentos anormalmente elevados.

Contexto

DBXen é um protocolo burn-to-earn: os usuários queimam tokens XEN em troca de recompensas DXN e uma parcela das taxas de protocolo acumuladas. O mecanismo principal funciona em ciclos. Quando um usuário chama burnBatch(), duas coisas acontecem: (1) a quantidade de XEN queimado é registrada sob o endereço do chamador (identificado por _msgSender()), e (2) o contrato XEN retorna ao DBXen via onTokenBurned() para atualizar os registros de ciclo do chamador (ciclo de queima e lastFeeUpdateCycle) para o ciclo atual.

Recompensas e taxas são liquidadas via updateStats(). A recompensa é proporcional à participação do usuário no total de XEN queimado em seu ciclo de queima. A taxa é baseada nas taxas de protocolo cumulativas que acumularam desde o último ciclo registrado do usuário. Ambos os cálculos dependem dos registros de ciclo do usuário estarem atualizados.

Análise de Vulnerabilidade

A causa raiz é a lógica de negócio falha no protocolo DBXen (0xf5c8...2abd). A função _msgSender() verifica se msg.sender é o forwarder. Se for, ela retorna os últimos 20 bytes do calldata, e esse valor retornado pode ser controlado arbitrariamente no contexto do forwarder. No entanto, burnBatch() queima diretamente o XEN mantido por msg.sender. Como resultado, um atacante pode invocar burnBatch() pelo forwarder, fazendo o protocolo queimar o XEN mantido pelo forwarder e atualizar tanto o registro de ciclo de queima quanto o registro de ciclo de atualização de taxas do forwarder para o ciclo atual. Ao mesmo tempo, porém, o protocolo registra a quantidade de XEN queimado sob o endereço correspondente a _msgSender().

Depois disso, o atacante chama claimFees(), que invoca updateStats(). Como os registros de ciclo do endereço _msgSender() nunca foram atualizados (tanto o ciclo de queima quanto lastFeeUpdateCycle permanecem em 0), updateStats() calcula recompensas no ciclo atual e computa taxas acumuladas desde o ciclo 0 - abrangendo todo o histórico de taxas do protocolo. O atacante então lucra chamando claimFees() e claimRewards().

Análise do Ataque

A análise a seguir é baseada na transação 0x914a5a...b808bc37.

  • Passo 1: O atacante primeiro chamou a função registerDomainSeparator() do contrato Forwarder, habilitando chamadas subsequentes a Forwarder.execute().

  • Passo 2: O atacante trocou 0,14e18 ETH por 13.900.000.000e18 XEN em um pool Uniswap V2.

  • Passo 3: O atacante transferiu 13.900.000.000e18 XEN para o contrato Forwarder.

  • Passo 4: O atacante usou Forwarder.execute() para aprovar o DBXen a gastar os 13.900.000.000e18 XEN mantidos pelo Forwarder.

  • Passo 5: O atacante usou Forwarder.execute() para chamar DBXen.burnBatch() e queimou 13.900.000.000e18 XEN. A quantidade queimada foi registrada sob o endereço 0x425D3eC2DCeBE2c04bA1687504D43AFC6be7328d, enquanto durante a execução da queima, XEN retornou ao DBXen via onTokenBurned(), atualizando os registros de ciclo relevantes no Forwarder.

  • Passo 6: O atacante usou Forwarder.execute() para chamar DBXen.claimFees() e obteve 65,36e18 ETH.

  • Passo 7: O atacante usou Forwarder.execute() para chamar DBXen.claimRewards() e cunhou 2.305,4e18 DXN.

Conclusão

A causa raiz deste incidente foi que o protocolo DBXen usou de forma inconsistente _msgSender() e msg.sender. Como esses dois valores podem diferir, a contabilidade interna do protocolo ficou inconsistente, o que permitiu que o atacante explorasse a discrepância para obter lucro.

Para reduzir riscos semelhantes no futuro:

  • Use _msgSender() de forma consistente em todos os caminhos de lógica, ou garanta que operações dependentes de msg.sender e a contabilidade dependente de _msgSender() sempre referenciem o mesmo endereço.

Incidente Goose Finance

Resumo

Em 15 de março de 2026, o Goose Finance, um protocolo de yield-farming na BNB Chain, foi explorado por cerca de $8K. A causa raiz foi uma falha na ordem de precificação de cotas em StrategyGooseEgg: deposit() cunha cotas antes de liquidar as recompensas colhidas na contabilidade, portanto o denominador do total de ativos usado para precificação de cotas exclui essas recompensas e é menor do que o valor real. Isso significa que os depositantes recebem mais cotas do que deveriam. Quando withdraw() é chamado, ele aciona uma colheita de recompensas que aumenta o total de ativos, tornando cada cota mais valiosa. Ao repetir depósito e retirada em uma única transação, o atacante cunhou repetidamente cotas com preço acima do real e as resgatou pelo valor corrigido (mais alto), extraindo a diferença como lucro.

Contexto

Goose Finance é um protocolo de yield-farming na BNB Chain onde os fundos dos usuários fluem de um vault para uma estratégia, que aposta ativos no MasterChef para ganhar recompensas EGG.

Os componentes relevantes para este incidente são:

  • VaultChef (0x3f64...): rastreia posições dos usuários e encaminha capital para StrategyGooseEgg.

  • StrategyGooseEgg (0x0980...): mantém a contabilidade no nível da estratégia com sharesTotal e wantLockedTotal.

  • MasterChef (0xe70e...): recebe ativos apostados e paga recompensas EGG.

  • WrappedEgg (0xb815...): encapsula EGG 1:1 em WEGG para apostas.

Operacionalmente, os depósitos são roteados do VaultChef para StrategyGooseEgg, depois apostados no MasterChef. As retiradas são iniciadas pelo VaultChef e executadas pela estratégia.

Uma expectativa contábil fundamental é que a precificação de cotas deve refletir o total de ativos da estratégia no momento da precificação (principal apostado mais recompensas inativas já mantidas pela estratégia). Em StrategyGooseEgg, no entanto, deposit() cunha cotas antes de _farm() liquidar ativos inativos em wantLockedTotal, enquanto withdraw() pode acionar a colheita de recompensas do MasterChef. Essa ordem é a base da vulnerabilidade analisada abaixo.

Análise de Vulnerabilidade

A causa raiz é uma dessincronização contábil em StrategyGooseEgg (0x0980...b26b) entre a colheita de recompensas e a precificação de cotas.

Em StrategyGooseEgg, a precificação de cotas usa wantLockedTotal como denominador: shares = deposit * sharesTotal / wantLockedTotal. Para que isso seja justo, wantLockedTotal deve refletir todos os ativos que a estratégia realmente possui, incluindo quaisquer recompensas EGG inativas no contrato. No entanto, deposit() cunha cotas antes de _farm() liquidar recompensas inativas em wantLockedTotal. Isso significa que o denominador exclui recompensas não contabilizadas e é menor do que o total real de ativos, fazendo com que o depositante receba mais cotas do que deveria.

Além disso, withdraw() chama MasterChef.withdraw(), que retorna o principal apostado mais as recompensas EGG pendentes para a estratégia. A contabilidade da estratégia apenas subtrai o _wantAmt solicitado de wantLockedTotal, portanto as recompensas colhidas permanecem no saldo da estratégia sem serem refletidas em wantLockedTotal. Isso amplia a lacuna entre os ativos realmente mantidos e o wantLockedTotal registrado, tornando qualquer precificação de cotas de deposit() subsequente ainda mais imprecisa.

Análise do Ataque

A análise a seguir é baseada na transação 0x86efdf...ce316223.

  • Passo 1: O atacante obteve um empréstimo flash de EGG de dois pares Pancake.

  • Passo 2: Primeiro depósito no VaultChef/StrategyGooseEgg (10.170.000e18 EGG).

  • Passo 3: Primeira retirada (12.593.884e18 EGG) colhe recompensas do MasterChef; 359.561e18 EGG é transferido para StrategyGooseEgg e permanece como valor inativo/não contabilizado (R > 0).

  • Passo 4: O segundo depósito reutiliza o capital retirado (12.593.884e18 EGG). As cotas são precificadas antes que o valor inativo seja liquidado, portanto este é o passo de supercunhagem.

  • Passo 5: A segunda retirada (12.826.027e18 EGG) realiza lucro das cotas supercunhadas (ou seja, 232.143 EGG acima do valor depositado no passo 4).

  • Passo 6: O atacante reembolsa os swaps flash e mantém o spread líquido.

Conclusão

O exploit decorre de uma falha na ordem de precificação de cotas em StrategyGooseEgg: deposit() cunha cotas antes de _farm() atualizar wantLockedTotal, enquanto withdraw() pode colher recompensas do MasterChef que permanecem temporariamente inativas e não contabilizadas. Isso permite que depósitos cunhem cotas contra um denominador desatualizado e depois retirem contra ativos atualizados.

Para reduzir riscos semelhantes no futuro:

  • Liquide recompensas e atualize a contabilidade antes dos cálculos de cunhagem e queima de cotas.

  • Precifique cotas contra um único totalAssets (apostado + inativo) no ponto exato de cálculo.

  • Adicione testes de invariante para shares_minted <= D * S / (A + R) sob condições de recompensas inativas não nulas.


Sobre a BlockSec

A BlockSec é um provedor completo de segurança blockchain e conformidade de criptoativos. Desenvolvemos produtos e serviços que ajudam nossos clientes a realizar auditoria de código (incluindo contratos inteligentes, blockchain e carteiras), 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 múltiplos artigos de segurança blockchain em conferências de prestígio, reportou vários ataques zero-day em aplicações DeFi, bloqueou múltiplos hacks para resgatar mais de 20 milhões de dólares e protegeu bilhões em criptomoedas.