Back to Blog

Perdas de ~3,87 milhões USD: ataques à Autheo Network e à UNCX | Resumo semanal da BlockSec

Code Auditing
22 de outubro de 2026
10 min read
Key Insights
  • Este relatório aborda 3 incidentes de segurança notáveis, com perdas totais estimadas em aproximadamente 3,87 milhões USD. Os incidentes envolveram o comprometimento de uma chave privada, um erro na contabilização do vesting e a geração fraca de sementes de carteiras.

  • O incidente com o Smart Token Vesting V2 da UNCX Network afetou 10 ativos nas redes Ethereum, BNB Chain, Base e Robinhood Chain. Uma falha no cálculo agregado de cotas misturou tranches de vesting criadas com diferentes proporções entre tokens e cotas, permitindo a extração de aproximadamente 807 mil USD em valor realizado ou encaminhado.

  • A contabilização em pool deve preservar a relação entre a quantidade de tokens, a base de cotas e o progresso do vesting de cada tranche. Combiná-las sob uma única proporção pode permitir que uma posição retire mais do que lhe cabe do saldo compartilhado, reduzindo os ativos disponíveis para atender às outras posições.

Durante a semana passada (12/10/2026–18/10/2026), destacam-se os seguintes 3 incidentes de segurança notáveis, com perda total estimada em aproximadamente 3,87 milhões USD.

Data Incidente Tipo Perda estimada
12/10/2026 Autheo Network Comprometimento de chave privada ~1,76 milhões USD
14/10/2026 UNCX Network Erro contábil ~807 mil USD
18/10/2026 Qubic Wallet Geração de seed fraca ~1,30 milhões USD

Os valores das perdas são estimativas baseadas no valor realizado.

  • UNCX Network: Este incidente é analisado em detalhes porque seu cálculo incorreto da quantidade agregada de cotas oferece a lição mais clara da semana sobre a contabilidade de contratos.

Verificação de segurança gratuita

Uma verificação rápida de segurança com nosso mecanismo interno de análise automatizada.

Faça uma verificação gratuita

Destaque da semana: UNCX Network

Em 14/10/2026, atacantes exploraram os contratos Smart Token Vesting V2 da UNCX Network nas redes Ethereum, BNB Chain, Base e Robinhood Chain. O incidente afetou 10 ativos em 11 transações de drenagem confirmadas e resultou em aproximadamente 807,609 mil USD em valor realizado ou encaminhado [1]. A causa raiz foi um cálculo incorreto da quantidade agregada de cotas que misturava parcelas de vesting criadas com diferentes proporções entre tokens e cotas. Isso fez os registros de cada posição divergirem da oferta global de cotas, permitindo que posições controladas pelos atacantes sacassem mais tokens do que eles haviam fornecido.

Contexto

A UNCX Network é uma provedora de infraestrutura DeFi multichain cujo produto Smart Token Vesting V2 permite que projetos bloqueiem tokens ERC-20 segundo cronogramas de vesting baseados em tempo. Cada cronograma é representado por um ID de vesting e registra o token, o criador, o beneficiário, o montante total, o montante já liberado e uma ou mais parcelas de desbloqueio. Uma posição flexível que permite aportes adicionais pode receber novas parcelas após sua criação, enquanto seu criador pode cancelar a parte ainda não adquirida.

As posições de um mesmo token compartilham um único saldo no VestingManager, e o protocolo usa cotas para determinar quanto desse saldo pertence a cada posição. Sua contabilidade, portanto, tem duas camadas relacionadas:

Global pool, per token
  create / topUp: receive tokens and mint shares
  release: convert releasable shares into tokens
  _totalTokenShares[token]: active shares across all positions

Position record, per vesting ID
  _depositedShares[id]: share basis assigned to the position
  totalAmount: token amount represented by its tranches
  released: shares already paid to the beneficiary

Para uma liberação, o gerenciador primeiro calcula quanto do cronograma já foi adquirido. Ele converte essa fração de totalAmount em cotas adquiridas usando _depositedShares[vestingId], subtrai released e chama o resultado de releasableShares. Na fórmula abaixo, tokenBalance é o saldo ERC-20 atual do gerenciador para o token do cronograma, compartilhado por todas as posições desse token, enquanto _totalTokenShares[token] é a oferta global correspondente de cotas ativas. A quantidade de tokens paga depende da proporção entre ambos:

amount = releasableShares * tokenBalance / _totalTokenShares[token]

Depositar por meio das funções de vesting aumenta tanto o saldo de tokens quanto a oferta de cotas. Uma transferência direta de tokens ERC-20 para o gerenciador altera apenas o saldo de tokens e, portanto, muda a quantidade de tokens representada por cada cota. Esse modelo de cotas só pode sustentar posições agrupadas se todas as operações do ciclo de vida mantiverem conciliados os registros das posições e a oferta global.

Análise da vulnerabilidade

O VestingManager afetado na Ethereum, em 0xA98F...99F5, contém um cálculo incorreto da quantidade agregada de cotas que se torna explorável por meio de release(), topUpVesting() e cancel(). Há implantações equivalentes afetadas nas outras três redes.

Para facilitar a comparação das mudanças de estado, a análise abaixo introduz D, R e V como abreviações. Esses símbolos não aparecem no código do contrato; a tabela associa cada um à variável ou expressão correspondente do contrato. Os valores associados se dividem em dois tipos de quantidade:

Símbolo Variável / expressão do contrato Tipo de quantidade Significado
D _depositedShares[vestingId] Quantidade de cotas Base cumulativa de cotas atribuída a todos os depósitos registrados para a posição
R schedule.released Quantidade de cotas Cotas cumulativas já pagas ao beneficiário
- schedule.totalAmount Montante de tokens Montante total de tokens registrado nas parcelas atuais da posição
- vestedAmount Montante de tokens Parte de totalAmount adquirida segundo essas parcelas até o momento atual
V vestedShare Quantidade de cotas Cotas adquiridas cumulativas calculadas pela aplicação de uma única proporção combinada de tokens adquiridos a D

Em release(), o contrato calcula releasableShares, incrementa schedule.released e subtrai a mesma quantidade de _totalTokenShares[schedule.token]. Ele não reduz _depositedShares[vestingId]. Assim, após uma liberação, a posição contribui com apenas D - R cotas ativas para a oferta global, embora sua base armazenada continue sendo D.

function release(uint256 vestingId) external nonReentrant {
    ...
    uint256 releasableShares = _getReleasableShares(vestingId);
    uint256 balance = IERC20(schedule.token).balanceOf(address(this));
    uint256 amount =
        (releasableShares * balance) / _totalTokenShares[schedule.token];

    schedule.released += releasableShares;
    _totalTokenShares[schedule.token] -= releasableShares;
    IERC20(schedule.token).safeTransfer(msg.sender, amount);
}

cancel() aplica uma única proporção combinada de aquisição à base agregada de cotas da posição. Primeiro, calcula vestedAmount por meio de getVestedAmount(vestingId); depois, converte a fração adquirida, denominada em tokens, em cotas como V = (vestedAmount * D) / totalAmount. Por fim, a função trata D - V como as cotas não adquiridas que podem ser removidas da posição. V é um valor cumulativo, não a quantidade de cotas adquiridas que resta após pagamentos anteriores.

function cancel(uint256 vestingId) external nonReentrant {
    ...
    uint256 vestedAmount = getVestedAmount(vestingId);
    uint256 remainingAmount = schedule.totalAmount - vestedAmount;

    uint256 vestedShare =
        vestedAmount * _depositedShares[vestingId] / schedule.totalAmount;
    uint256 sharesToRemove =
        _depositedShares[vestingId] - vestedShare;
    _totalTokenShares[schedule.token] -= sharesToRemove;

    _depositedShares[vestingId] = vestedShare;
    schedule.totalAmount = vestedAmount;
    schedule.cancelled = true;

    IERC20(schedule.token).safeTransfer(schedule.creator, remainingAmount);
}

A falha está no próprio cálculo combinado. Cada parcela tem seu próprio montante de tokens, progresso de aquisição e número de cotas emitidas segundo a proporção do pool vigente quando foi adicionada. Portanto, suas cotas adquiridas deveriam ser calculadas separadamente e depois somadas. Em vez disso, o contrato primeiro agrega os montantes de tokens e a base de cotas e depois aplica uma única proporção de aquisição à posição combinada, pois armazena apenas um D e um totalAmount. As duas abordagens só são equivalentes quando as parcelas têm a mesma proporção de cotas por token ou o mesmo progresso de aquisição. Uma transferência direta de tokens altera a proporção do pool antes de um aporte adicional; assim, a nova parcela recebe muito menos cotas por token do que a parcela original. Aplicar uma única proporção combinada de aquisição às duas parcelas pode, então, produzir um V que já não representa as cotas adquiridas pelas duas em conjunto.

Como R já foi pago a partir das cotas adquiridas cumulativas da posição, um cronograma consistente deve satisfazer R <= V. A violação dessa desigualdade é o sintoma direto do cálculo combinado. O código não mantém um saldo separado de cotas ativas para a posição; ele subtrai D - V diretamente do _totalTokenShares global. Suponha que outras posições contribuam com 1000 cotas, enquanto esta posição tenha D = 100 e R = 80. Antes do cancelamento, a oferta global contém 1,000 + (D - R) = 1,020 cotas. Se o cálculo combinado produzir V = 60, cancel() subtrai D - V = 40 e deixa apenas 980 cotas globais, 20 a menos do que as 1000 cotas contribuídas pelas outras posições. Se a oferta global contivesse apenas as 20 cotas ativas desta posição, subtrair 40 causaria um underflow e reverteria a transação; a dedução excedente só é bem-sucedida porque o pool compartilhado contém cotas contribuídas por outras posições. A função então armazena _depositedShares[vestingId] = 60, enquanto schedule.released permanece em 80, deixando o registro da posição no estado impossível D < R.

O código não impõe R <= V nem limita sharesToRemove às cotas ativas da posição. Portanto, o cancelamento pode subtrair cotas históricas que já não fazem parte da oferta global. Ao mesmo tempo, transfere totalAmount - vestedAmount diretamente em unidades de token, sem converter o reembolso pela proporção de cotas do pool. O cancelamento, por si só, não produz um pagamento excedente; ele apenas reduz a oferta global de cotas para abaixo das cotas ativas registradas nas posições, criando a condição para um resgate excessivo posterior.

Análise do ataque

A análise a seguir usa uma transação representativa na Ethereum, 0x352065...c23823. O contrato de ataque executou a mesma sequência principal contra cinco tokens dentro da transação, usando liquidez de flash loans para cada token.

  • Etapa 1: O atacante criou uma pequena posição com aquisição imediata, #5058, e duas posições muito maiores com aquisição imediata, flexíveis e que permitiam aportes adicionais, #5059 e #5060. A pequena posição seria mantida para o saque final, enquanto as grandes posições forneceriam os registros históricos de cotas necessários para corromper a contabilidade global.

  • Etapa 2: O atacante liberou as duas grandes posições. Na proporção inicial do pool, próxima de 1:1, cada liberação devolveu aproximadamente seu depósito líquido. As cotas correspondentes saíram de _totalTokenShares, mas os valores originais permaneceram nos registros de _depositedShares das posições #5059 e #5060 e foram registrados como liberados apenas no campo separado released de cada cronograma.

  • Etapa 3: O atacante transferiu tokens diretamente para o VestingManager. Isso recompôs o saldo de tokens sem restaurar as cotas liberadas, de modo que a quantidade de tokens representada por cada cota restante aumentou acentuadamente.

  • Etapa 4: O atacante fez aportes adicionais nas grandes posições já liberadas. Como os aportes foram precificados pela proporção, agora elevada, de tokens por cota, depósitos substanciais de tokens emitiram apenas um pequeno número de novas cotas. As posições passaram a combinar uma grande base histórica desatualizada com uma pequena contribuição de cotas ativas.

  • Etapa 5: O atacante cancelou as duas posições que haviam recebido aportes adicionais. Cada cancelamento aplicou uma única proporção combinada de aquisição ao histórico agregado de cotas depositadas. O vestedShare recalculado ficou abaixo das cotas já liberadas, de modo que sharesToRemove excedeu a contribuição ativa da posição. A oferta global foi reduzida em cerca de 5.0e25 cotas fictícias por posição, embora cada posição tivesse adicionado apenas uma pequena quantidade de cotas ativas durante o aporte adicional.

  • Etapa 6: Com o denominador muito subestimado, o atacante liberou a pequena posição mantida. No exemplo de token analisado, um depósito líquido de aproximadamente 5.73e22 unidades brutas permitiu sacar aproximadamente 1.07e26 unidades brutas, cerca de 1,873 vezes o depósito. O atacante repetiu ciclos de criação e liberação explorando a proporção distorcida e quitou a liquidez obtida por flash loans.

O relatório oficial do incidente identificou o mesmo padrão em 11 transações de drenagem confirmadas. Algumas variantes foram concluídas atomicamente, enquanto outras realizaram a liberação final em uma transação vinculada. A UNCX calculou o valor aproximado de 807,609 mil USD a partir dos recursos realizados ou encaminhados, após excluir o capital dos atacantes, o volume temporário de flash loans e os ativos devolvidos [1].

Comece a usar o Phalcon Explorer

Explore as transações para agir com critério

Experimente gratuitamente agora

Conclusão

A causa raiz foi uma falha na contabilidade das cotas agregadas. O protocolo combinou parcelas criadas com diferentes proporções entre tokens e cotas e, em seguida, aplicou uma única proporção de aquisição à base agregada de cotas dessas parcelas. Isso calculou incorretamente as cotas adquiridas cumulativas, permitiu que uma posição removesse mais cotas do que ainda contribuía e fez os registros de cada posição divergirem da oferta global de cotas. Os atacantes exploraram a oferta global subestimada para sacar do saldo compartilhado tokens que continuavam necessários para atender a outras posições de vesting.

Um modelo contábil que permita depósitos posteriores com proporções de cotas variáveis deve preservar o montante de tokens, a base de cotas e o progresso de aquisição de cada parcela, em vez de combiná-los sob uma única proporção. Também deve garantir que os pagamentos cumulativos nunca excedam as cotas adquiridas cumulativas e que uma posição não possa remover mais do que sua contribuição ativa.

Referências

[1] Relatório do incidente do Smart Token Vesting V2 da UNCX Network

Melhor auditoria de segurança para Web3

Valide o design, o código e a lógica de negócios antes do lançamento

Best Security Auditor for Web3

Validate design, code, and business logic before launch. Aligned with the highest industry security standards.

BlockSec Audit