O invasor usou um saldo grande, previamente acumulado, de um token auxiliar especial para solicitar reembolsos muito superiores ao valor de um novo depósito. A gravação dos valores numéricos enormes resultantes fez com que o evento de reembolso excedesse o limite de log da NEAR de 16,384 bytes. Por causa disso, o manipulador de depósitos do Intents não conseguiu concluir a operação. Após a falha, o HOT devolveu os ativos originais que respaldavam o depósito, incluindo USDT, mas o saldo interno do Intents creditado por uma operação bem-sucedida anterior permaneceu em vigor.
Endereços e suas funções
| Rede | Endereço ou conta | Função |
|---|---|---|
| BSC | 0x09fd1f5d9f185067a92493e43aa259ea4ab3ad37 | O invasor, que recebeu cerca de $3.865M |
| BSC | 0x233c5370CCfb3cD7409d9A3fb98ab94dE94Cb4Cd | Tesouro do HOT Bridge |
| NEAR | 20e6bfe4685d99004fc2095d3a0fc6158cd810ce8f801196141e183ed6e19d65 | A conta do invasor, a partir da qual os depósitos em lote e as solicitações de saque foram iniciados; também a destinatária dos ativos devolvidos que respaldavam o depósito |
| NEAR | dd1fe58ec316ada5b673b88c14fe2fa2b7ede82080440901dfbd64b994241757 | O contrato do invasor, destinatário do saldo interno e contrato de callback para notificações |
| NEAR | 873518f0f5cf120d7a8e2a416337bba16d7ae622e91f7da3862f07e89dc2ba9e | O contrato do invasor que se tornou o destinatário do saldo interno nas sequências de operações subsequentes de 1 de outubro que puderam ser confirmadas. Forma abreviada: 8735… |
| NEAR | v2_1.omni.hot.tg | O contrato da ponte HOT e o registro das representações multi-token NEP-245 |
| NEAR | intents.near | O contrato verificador do Intents e o registro interno dos saldos dos usuários |
Contexto
O HOT Bridge, também chamado de HOT Omni Bridge, conecta ativos de blockchains externas à rede NEAR e está integrado ao NEAR Intents para trocas de tokens. O SDK público do projeto é @hot-labs/omni-sdk.
O sistema envolve três níveis de contabilidade: o ativo real na blockchain externa, mantido no tesouro do HOT → a representação desse ativo emitida pelo HOT na NEAR → o saldo interno do usuário no intents.near.
Um depósito normal de entrada passa pelas seguintes etapas:
- O usuário deposita um ativo real na blockchain de origem.
- O HOT verifica o depósito e autoriza a representação correspondente do ativo na NEAR.
- O HOT emite (mint) os tokens correspondentes na NEAR. Esses tokens são transferidos para o Intents, onde o valor correspondente é refletido no saldo interno do usuário.
O código de processamento de depósitos do Intents também oferece suporte a uma notificação opcional ao destinatário. O sistema primeiro credita os fundos na conta e, em seguida, chama a aplicação destinatária. Essa aplicação pode solicitar o reembolso de parte do valor depositado.
Em um reembolso normal, o Intents deve primeiro remover o valor correspondente do saldo interno e, em seguida, devolver os ativos que respaldam esse saldo. O manipulador queima (burn) o crédito não utilizado, ou seja, cancela a parte correspondente do saldo interno, e informa ao HOT quais valores permaneceram não utilizados. O HOT então devolve os tokens correspondentes ao remetente original.
Durante o incidente, o vínculo entre o segundo e o terceiro níveis de contabilidade se rompeu: o HOT devolveu o respaldo do depósito como tokens de representação na NEAR, mas o Intents manteve o saldo interno do usuário. Isso posteriormente possibilitou solicitar saques do tesouro por meio do mecanismo normal de saque da ponte.
1. Preparação
Em 29 de setembro, o invasor recebeu 10 USDT da BSC na NEAR na transação 9SiSBv….
O invasor também transferiu da rede Gonka 97 × 10³⁶ + 97 unidades mínimas do ativo auxiliar especial ctfhote6ae2a90d1836ef1. O depósito original corresponde ao recebimento posterior do ativo na NEAR na transação Fmwao…. Como resultado, tokens de representação do HOT foram emitidos para o endereço 20e6….
Em seguida, na transação 4wf2…, o invasor depositou 97 × 10³⁶ unidades do ativo auxiliar no Intents para o contrato destinatário do ataque dd1…. 97 unidades mínimas permaneceram fora do Intents. Isso criou o grande saldo interno necessário para as solicitações de reembolso superdimensionadas posteriores.
2. Exploração da vulnerabilidade
A primeira operação de exploração confirmada, 3k91…, começou em 29 de setembro às 16:14:19.280 UTC.
O invasor chamou a função HOT mt_batch_transfer_call, depositando no Intents 97 entradas do token auxiliar de uma unidade cada, junto com 10 USDT. O Intents creditou com sucesso os fundos ao saldo interno de dd1... antes de notificar o contrato destinatário controlado pelo invasor. O contrato destinatário solicitou reembolsos de 97 quantias do token auxiliar, de 10³⁶ unidades cada, e então indicou que nenhum reembolso em USDT era necessário.
O manipulador de reembolso vulnerável limitava o valor do reembolso ao saldo disponível, mas não considerava o tamanho do depósito atual. Como resultado, o saldo previamente preparado possibilitou incluir valores enormes de reembolso no evento de queima do token.
Aqui, a variável balance_left representa todo o saldo interno do destinatário em um determinado ativo, incluindo depósitos anteriores. A verificação determinava se o destinatário tinha fundos suficientes no saldo, mas não limitava o reembolso ao valor depositado na operação atual.
Como resultado, uma entrada de depósito de apenas uma unidade podia gerar uma solicitação de reembolso de 10³⁶ unidades, caso o saldo previamente acumulado do destinatário pudesse cobrir esse valor. Cada valor de reembolso diferente de zero calculado era então adicionado ao evento de queima do token, nas linhas 198-199 do código acima.
O tamanho do evento resultante foi de 16,495 bytes:
| Componente | Cálculo | Bytes |
|---|---|---|
| 97 IDs completos de token, 125 caracteres cada | 97 × (125 caracteres + 2 aspas) + 96 vírgulas | 12,415 |
| 97 valores de reembolso, cada um uma string entre aspas de 37 dígitos | 97 × (37 dígitos + 2 aspas) + 96 vírgulas | 3,879 |
| Prefixo EVENT_JSON, metadados, ID do proprietário, memo e outros caracteres JSON | Tamanho fixo | 201 |
| Total | 12,415 + 3,879 + 201 | 16,495 |
O erro na formação do evento reverteu as alterações de saldo anteriores feitas pelo manipulador de reembolso. O HOT então devolveu todo o lote de depósito original, incluindo os 10 USDT. O manipulador de depósito havia chamado self.deposit(...) antes de agendar a notificação ao destinatário e a chamada subsequente a mt_resolve_deposit.
Assim, a falha de uma chamada posterior não desfaz automaticamente as alterações feitas com sucesso em uma chamada anterior. Esse comportamento está descrito na documentação do NEAR. O saldo interno do Intents creditado durante a etapa anterior bem-sucedida permaneceu em vigor.
3. Ampliando a escala
Em uma das operações posteriores, 3mst2uZ32KPuCs7wc8rJqEtCrZqugCy4yDSZ3df8KPm3, o invasor depositou 649,984 USDT e solicitou um reembolso exatamente do mesmo valor ao remetente.
O histórico de transações reconstruído contém 23 chamadas em lote vinculadas a falhas no manipulador de reembolso. O padrão completo, em que o saldo interno foi retido enquanto o suporte do depósito foi devolvido, foi confirmado de forma confiável em seis casos.
Problema de sincronização: como o protocolo Verus perdeu $7.5M devido a diferenças entre blockchains3 de ago. de 2026Leia mais4. Retirando os fundos
O invasor então enviou solicitações de retirada do NEAR através do HOT, especificando o endereço BSC 0x09fd…ad37 como destinatário. Cinco solicitações correspondem a pagamentos de USDT bem-sucedidos do tesouro do HOT na BSC em 0x233c…4Cd.
| Horário do pagamento na BSC, UTC | USDT | Solicitação no NEAR | Pagamento na BSC |
|---|---|---|---|
| 30 de setembro, 23:54:30 | 800,000 | A1tNAg5w3NVrDGheG9zTNmrXeWd4249nYyVLF8ekK3Xh | 0x9fe58e031f73bbd880c782bc9e7446bcda32cadb304a729209871a4a81856c4c |
| 1 de outubro, 00:24:02 | 1,200,000 | 799TeaBfrtCcmJ5dMT6fqupqLHyZQAqm2ksKoEfyMj7J | 0x0381265d6a1bb09de899f49f410a8b907cd548358a7e3c91076c3a52656b8220 |
| 1 de outubro, 00:50:42 | 1,500,000 | GJ65AKDXN9ov7DTFgmgvbykNww1B5J7jK42A4QfbHgon | 0x69d1c68c7e961a0199d3c9f3b6a31cb168ed775ef34b51a42762253ac1efcceb |
| 1 de outubro, 01:46:20 | 330,000 | E4gEvzNSPjeiah5c6pvUWTXkUmiiwjkbWr2zzQWsz6it | 0x9c10b967da0c85631ec105e3b322c0a851fcd01b69ff390ff58f860e5cce003a |
| 1 de outubro, 06:08:26 | 35,000 | Avf5qSFyndojAP7aYYMw2sCUZj2JSHUxYhK8CVGisy8B | 0x16ddfa695b9525662b8e57c35723f44af89b4e7fc2dedc3634f730caf8b90c02 |
| Total | 3,865,000 |
A vulnerabilidade
1. O saldo interno é creditado antes de o destinatário responder à solicitação de reembolso
A implementação do Intents para lidar com depósitos recebidos primeiro chama:
self.deposit( receiver_id.clone(), core_token_ids .clone() .zip(amounts.iter().map(|amount| amount.0)), Some(«deposit»), ) .unwrap_or_else(|err| err.panic());
Para um depósito com notificação, o sistema agenda então uma chamada ao contrato do destinatário e, em seguida, uma chamada a mt_resolve_deposit. A etapa que cria o saldo interno pode ser concluída com sucesso antes que o manipulador de reembolso subsequente gere um erro. O resultado final da execução informa à HOT que a operação posterior falhou, mesmo que o saldo interno criado anteriormente já exista.
2. O cálculo vulnerável permite um reembolso maior do que o depósito atual
O commit 35044fd… da NEAR, de 1 de outubro, intitulado «chore: TEMP change that reflects deployed state», reproduz a lógica de reembolso da seguinte forma:
let requested_refund = requested_refund.unwrap_or(deposited); let balance_left = receiver.token_balances.amount_for(&token_id); let refund_amount = balance_left.min(requested_refund);*
Fonte: resolve_deposit_internal.
Esse cálculo leva em conta o saldo disponível, mas não limita o reembolso ao valor do depósito atual. O loop adiciona cada valor de reembolso não nulo ao evento de burn, ao mesmo tempo em que o subtrai do saldo do destinatário e do fornecimento total interno do token. Depois disso, é feita a seguinte chamada:
MtEvent::MtBurn([burn_event].as_slice().into()).emit();
Essas alterações contábeis são feitas dentro do recibo do manipulador de reembolso. Se a gravação do evento falhar, todas as alterações feitas nesse recibo são revertidas junto com ele.
3. Uma possível discrepância entre o código publicado e a versão implantada
A falta de limite do valor de reembolso ao tamanho do depósito atual foi corrigida no repositório público em 23 de março de 2026, às 12:47:07 UTC. A correção foi publicada no PR #240 / commit 015fa811…, intitulado «fix: cap notify refunds by the original amounts». Na versão corrigida, o cálculo é assim:
let refund_amount = requested_refund.min(deposited).min(balance_left);*
Para as entradas auxiliares de uma unidade usadas no ataque, isso limita cada reembolso a no máximo uma unidade, independentemente do saldo acumulado anteriormente. Esse mecanismo impede que os valores de reembolso sejam multiplicados por números excessivamente grandes. Também restaura a regra fundamental da contabilidade de depósitos: um reembolso referente à operação atual não pode retirar fundos depositados em depósitos anteriores.
O commit temporário de 1 de outubro remove esse limite para reproduzir o comportamento da versão implantada. A observação do teste de regressão de outubro da NEAR afirma explicitamente que eventos de reembolso com valores excessivos fizeram o callback falhar «como aconteceu na versão implantada».
Resposta e recuperação
Após o incidente, foi realizada uma atualização, refletida na transação FPLaCtd…. A transação principal, executada em 2 de outubro às 20:31:41.711 UTC, chama ctl-intents.near.upgrade para o contrato intents.near. O evento do controlador lista a versão de lançamento 0.4.4.
Um recibo de implantação aninhada bem-sucedido, registrado às 20:31:45.442 UTC, implantou o código e executou a função state_migrate. Alex Shevchenko declarou publicamente que o valor total de $3.86M havia sido devolvido.

