Back to Blog

Потери на сумму около 3,87 млн USD: взломы Autheo Network и UNCX | Еженедельный обзор BlockSec

Code Auditing
22 октября 2026 г.
9 min read
Key Insights
  • В этом отчёте рассматриваются 3 значимых инцидента безопасности с совокупными оценочными потерями приблизительно 3,87 млн USD. Они были связаны с компрометацией закрытого ключа, ошибкой учёта вестинга и генерацией недостаточно стойких сидов кошельков.

  • Инцидент со Smart Token Vesting V2 сети UNCX Network затронул 10 активов в Ethereum, BNB Chain, Base и Robinhood Chain. Ошибка в расчёте совокупного количества долей привела к смешению траншей вестинга, созданных при разных соотношениях токенов и долей, что позволило извлечь активы с реализованной стоимостью или стоимостью переведённых активов приблизительно 807 тыс. USD.

  • При учёте объединённых средств необходимо сохранять связь между количеством токенов в каждом транше, базой расчёта его долей и ходом вестинга. Объединение траншей с применением одного соотношения может позволить вывести по одной позиции больше средств, чем ей причитается из общего баланса, сократив объём активов, доступных для исполнения обязательств по другим позициям.

За прошедшую неделю (12.10.2026–18.10.2026) отмечены следующие 3 значимых инцидента безопасности с совокупным оценочным ущербом примерно 3,87 млн USD.

Дата Инцидент Тип Оценочный ущерб
12.10.2026 Autheo Network Компрометация приватного ключа ~1,76 млн USD
14.10.2026 UNCX Network Ошибка учёта ~807 тыс. USD
18.10.2026 Qubic Wallet Генерация слабой сид-фразы ~1,30 млн USD

Размеры ущерба оценены на основе фактически реализованной стоимости.

  • UNCX Network: Этот инцидент подробно разобран, поскольку ошибка в расчёте совокупного количества долей даёт самый наглядный за неделю урок по учёту в контрактах.

Бесплатная проверка безопасности

Быстрая проверка безопасности с помощью нашего собственного механизма автоматизированного анализа.

Проверить бесплатно

Главный инцидент недели: UNCX Network

14.10.2026 злоумышленники использовали уязвимость контрактов Smart Token Vesting V2 сети UNCX Network в Ethereum, BNB Chain, Base и Robinhood Chain. Инцидент затронул 10 активов в 11 подтверждённых транзакциях вывода средств и привёл к получению или переводу активов стоимостью примерно 807,609 тыс. USD [1]. Первопричиной стала ошибка в расчёте совокупного количества долей: при расчёте смешивались транши вестинга, созданные при разных соотношениях токенов и долей. В результате записи отдельных позиций перестали соответствовать общему количеству долей, что позволило позициям под контролем злоумышленников вывести больше токенов, чем те внесли.

Общая информация

UNCX Network — поставщик инфраструктуры DeFi для нескольких блокчейнов. Его продукт Smart Token Vesting V2 позволяет проектам блокировать токены ERC-20 по графикам вестинга, привязанным ко времени. Каждый график представлен идентификатором вестинга и содержит сведения о токене, создателе, бенефициаре, общей сумме, уже выданной сумме и одном или нескольких траншах разблокировки. В позицию типа soft, допускающую пополнение, после её создания можно добавлять новые транши, а её создатель может отменить вестинг ещё не разблокированной части.

Позиции одного и того же токена используют общий баланс в VestingManager, а протокол определяет принадлежащую каждой позиции часть этого баланса через доли. Поэтому учёт ведётся на двух связанных уровнях:

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

При выдаче менеджер сначала рассчитывает, какая часть суммы по графику прошла вестинг. Затем он переводит соответствующую долю от totalAmount в доли, прошедшие вестинг, с использованием _depositedShares[vestingId], вычитает released и называет результат releasableShares. В приведённой ниже формуле tokenBalance — текущий баланс менеджера в токене ERC-20, указанном в графике; этот баланс является общим для всех позиций по данному токену. При этом _totalTokenShares[token] — соответствующее общее количество активных долей. Количество выдаваемых токенов зависит от их соотношения:

amount = releasableShares * tokenBalance / _totalTokenShares[token]

Внесение средств через функции вестинга увеличивает и баланс токенов, и общее количество долей. Прямой перевод токенов ERC-20 менеджеру изменяет только баланс токенов, а значит, и количество токенов, соответствующее каждой доле. Такая модель долей может поддерживать позиции в общем пуле только при условии, что на каждом этапе жизненного цикла записи позиций согласуются с общим количеством долей.

Анализ уязвимости

В затронутом контракте VestingManager в Ethereum по адресу 0xA98F...99F5 есть ошибка в расчёте совокупного количества долей, которой можно воспользоваться при последовательном вызове release(), topUpVesting() и cancel(). Аналогичные затронутые развёртывания существуют и в трёх других сетях.

Чтобы было проще сопоставлять изменения состояния, в анализе ниже введены сокращённые обозначения D, R и V. В коде контракта этих обозначений нет; таблица сопоставляет каждое из них с соответствующей переменной или выражением контракта. Сопоставленные значения относятся к двум типам величин:

Обозначение Переменная / выражение контракта Тип величины Значение
D _depositedShares[vestingId] Количество долей Совокупная база долей, назначенная всем депозитам, учтённым для позиции
R schedule.released Количество долей Совокупное количество долей, уже выплаченных бенефициару
- schedule.totalAmount Количество токенов Общее количество токенов, учтённое по текущим траншам позиции
- vestedAmount Количество токенов Часть totalAmount, прошедшая вестинг по этим траншам к текущему моменту
V vestedShare Количество долей Совокупное количество долей, прошедших вестинг, рассчитанное путём применения единого смешанного коэффициента вестинга токенов к D

В release() контракт рассчитывает releasableShares, увеличивает schedule.released и вычитает ту же величину из _totalTokenShares[schedule.token]. При этом он не уменьшает _depositedShares[vestingId]. Поэтому после выдачи позиция вносит в общее количество долей лишь D - R действующих долей, хотя сохранённая для неё база остаётся равной 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() применяет единый смешанный коэффициент вестинга к совокупной базе долей позиции. Сначала функция рассчитывает vestedAmount через getVestedAmount(vestingId), затем переводит выраженную в токенах долю, прошедшую вестинг, в доли как V = (vestedAmount * D) / totalAmount. Наконец, функция считает D - V количеством не прошедших вестинг долей позиции, которое можно удалить. V — это совокупная величина, а не остаток долей, прошедших вестинг после предыдущих выплат.

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

Ошибка заключается в самом смешанном расчёте. У каждого транша есть собственное количество токенов, прогресс вестинга и количество долей, выпущенных по действовавшему при добавлении этого транша соотношению в пуле. Поэтому доли каждого транша, прошедшие вестинг, следует рассчитывать отдельно, а затем суммировать. Вместо этого контракт сначала объединяет количества токенов и базы долей, а затем применяет единый коэффициент вестинга ко всей позиции, поскольку хранит только одно значение D и одно значение totalAmount. Оба подхода дают одинаковый результат только в том случае, если у траншей одинаковое количество долей на токен или одинаковый прогресс вестинга. Прямой перевод токенов меняет соотношение в пуле до пополнения, поэтому новый транш получает значительно меньше долей на токен, чем первоначальный. Применение единого смешанного коэффициента вестинга к обоим траншам может дать значение V, которое уже не отражает их совокупное количество долей, прошедших вестинг.

Поскольку R уже выплачено из совокупного количества долей позиции, прошедших вестинг, согласованный график должен удовлетворять условию R <= V. Нарушение этого неравенства — прямой признак ошибки смешанного расчёта. Код не ведёт отдельный учёт остатка действующих долей позиции: он вычитает D - V непосредственно из общего значения _totalTokenShares. Предположим, другие позиции вносят 1000 долей, а у рассматриваемой позиции D = 100 и R = 80. До отмены общее количество включает 1,000 + (D - R) = 1,020 долей. Если смешанный расчёт даёт V = 60, cancel() вычитает D - V = 40, оставляя в общем количестве лишь 980 долей — на 20 меньше, чем 1000 долей, внесённых другими позициями. Если бы общее количество включало только 20 действующих долей этой позиции, вычитание 40 привело бы к переполнению при вычитании и откату транзакции; избыточное вычитание проходит только потому, что в общем пуле есть доли, внесённые другими позициями. Затем функция сохраняет _depositedShares[vestingId] = 60, тогда как schedule.released остаётся равным 80, и запись позиции оказывается в невозможном состоянии D < R.

Код не обеспечивает соблюдения R <= V и не ограничивает sharesToRemove количеством действующих долей позиции. Поэтому при отмене могут вычитаться исторические доли, которых уже нет в общем количестве долей. Одновременно функция напрямую переводит totalAmount - vestedAmount в токенах, не пересчитывая возвращаемую сумму по соотношению долей в пуле. Сама отмена не приводит к избыточной выплате: она лишь уменьшает общее количество долей ниже суммы действующих долей, учтённых по позициям, создавая условия для последующего избыточного вывода.

Анализ атаки

В следующем анализе рассмотрена одна показательная транзакция в Ethereum — 0x352065...c23823. В рамках этой транзакции атакующий контракт выполнил одну и ту же основную последовательность действий с пятью токенами, используя для каждого из них флеш-ликвидность.

  • Шаг 1: Злоумышленник создал небольшую позицию #5058 с немедленным вестингом и две значительно более крупные позиции #5059 и #5060 с немедленным вестингом, типа soft и с возможностью пополнения. Небольшую позицию предполагалось сохранить для заключительного вывода, а крупные позиции обеспечивали исторические записи о долях, необходимые для нарушения общего учёта.

  • Шаг 2: Злоумышленник вывел средства из двух крупных позиций. При первоначальном соотношении в пуле, близком к 1:1, из каждой позиции вернулась сумма, приблизительно равная её чистому депозиту. Соответствующие доли были вычтены из _totalTokenShares, но первоначальные значения сохранились в записях _depositedShares для позиций #5059 и #5060, а как выданные они были учтены только в отдельном поле released каждого графика.

  • Шаг 3: Злоумышленник напрямую перевёл токены в VestingManager. Это восстановило баланс токенов, но не количество выведенных долей, поэтому количество токенов, соответствующее каждой оставшейся доле, резко выросло.

  • Шаг 4: Злоумышленник пополнил крупные позиции, из которых уже были выведены средства. Поскольку пополнения оценивались по новому, повышенному соотношению токенов на долю, значительные депозиты токенов привели к выпуску лишь небольшого количества новых долей. Теперь позиции сочетали большую устаревшую историческую базу с небольшим вкладом действующих долей.

  • Шаг 5: Злоумышленник отменил две пополненные позиции. При каждой отмене к совокупной истории внесённых долей применялся единый смешанный коэффициент вестинга. Пересчитанное значение vestedShare оказалось меньше количества уже выданных долей, поэтому sharesToRemove превысило вклад действующих долей позиции. Общее количество долей уменьшилось примерно на 5.0e25 фиктивных долей на позицию, хотя при пополнении каждая позиция добавила лишь небольшое количество действующих долей.

  • Шаг 6: После того как знаменатель оказался сильно занижен, злоумышленник вывел средства из сохранённой небольшой позиции. В рассматриваемом примере с одним токеном чистый депозит примерно в 5.73e22 единиц базового номинала позволил вывести примерно 1.07e26 единиц базового номинала — примерно в 1,873 раз больше внесённого. Злоумышленник повторил циклы создания позиций и вывода средств при искажённом соотношении, а затем вернул флеш-ликвидность.

В официальном отчёте об инциденте такая же схема выявлена в 11 подтверждённых транзакциях вывода средств. Некоторые варианты выполнялись атомарно, а в других заключительный вывод происходил в связанной транзакции. UNCX рассчитала сумму примерно в 807,609 тыс. USD на основе фактически полученных или переведённых средств, исключив собственные средства злоумышленника, временные объёмы флеш-ликвидности и возвращённые активы [1].

Начните работу с Phalcon Explorer

Изучайте транзакции, чтобы принимать взвешенные решения

Попробовать бесплатно

Заключение

Первопричиной была ошибка в учёте совокупного количества долей. Протокол объединял транши, созданные при разных соотношениях токенов и долей, а затем применял единый коэффициент вестинга к их совокупной базе долей. Это приводило к неверному расчёту совокупного количества долей, прошедших вестинг, позволяло вычесть для позиции больше долей, чем она продолжала вносить, и нарушало соответствие записей отдельных позиций общему количеству долей. Злоумышленники воспользовались заниженным общим количеством долей, чтобы вывести из общего баланса токены, которые по-прежнему были необходимы для обеспечения других позиций вестинга.

Модель учёта, допускающая последующие депозиты при изменяющемся соотношении долей, должна сохранять для каждого транша его количество токенов, базу долей и прогресс вестинга, а не объединять их под одним коэффициентом. Она также должна обеспечивать, чтобы совокупные выплаты никогда не превышали совокупное количество долей, прошедших вестинг, и чтобы из позиции нельзя было вычесть больше её вклада действующих долей.

Источники

[1] Отчёт UNCX Network об инциденте со Smart Token Vesting V2

Лучший аудитор безопасности для Web3

Проверьте архитектуру, код и бизнес-логику перед запуском

Sign up for the latest updates
Потеряно около $542 тыс.: эксплуатация уязвимости MakerDaoBot | Еженедельный обзор BlockSec
Security Insights

Потеряно около $542 тыс.: эксплуатация уязвимости MakerDaoBot | Еженедельный обзор BlockSec

В Ethereum за 05–11.10.2026 произошёл 1 инцидент; ущерб — около $542 тыс. Атакующий передал вредоносный адаптер функции MakerDaoBot без контроля доступа. Получив права на балансы MakerDAO, адаптер закрыл 4 аукциона ETH-A, конвертировал 200 ETH-A в WETH; атакующий вывел 200 ETH.

~418 млн $ потеряно: эксплойты Bitget и NEAR Intents | BlockSec Weekly
Security Insights

~418 млн $ потеряно: эксплойты Bitget и NEAR Intents | BlockSec Weekly

Этот отчёт за две недели фиксирует 8 инцидентов с 21 сентября по 4 октября 2026 г., убытки ≈$418,4М в Ethereum, BNB Chain, Solana, Bitcoin, TRON, XRP Ledger, Zcash, Avalanche, NEAR. Разбор: уязвимость продукта безопасности Bitget ($387,5М) и ошибка NEAR Intents ($3,87М).

Взлом Bitget на $387,5 млн вне блокчейна: не только ключи и контракты
Security Insights

Взлом Bitget на $387,5 млн вне блокчейна: не только ключи и контракты

24.09.2026 хакеры взломали сторонний продукт, похитили учётные данные и подделали выводы. Похищено ~$387,5 млн со счетов Bitget (Ethereum, EVM, XRP Ledger, Zcash, TRON); ключи целы. Обзор инцидента, конвертации активов, мер защиты и blockchain-пентеста.

Best Security Auditor for Web3

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

BlockSec Audit