К основному содержанию
T TON Adoption
Безопасность FORENSICS · 2026

TAC Bridge Drain 2026: разбор атаки на $2.5M+ в TON

Технический forensics-разбор drain-атаки на мост TAC 11 мая 2026: хендлер 0x0E50D313, отсутствие проверки подписей валидаторов

Автор
· research lead · security desk
Опубликовано
10 мин. чтения

11 мая 2026 года в 09:14 UTC началась drain-атака на admin-контракт моста TAC в TON. К моменту публикации этого разбора (14 мая) drain всё ещё активен: с моста выведено более 2.5 млн долларов в обёрнутых джеттонах (USDT, BLUM, tsTON и др.) плюс 384 тыс. свежевыпущенных TAC. Ни один официальный канал TAC не публиковал ни предупреждения, ни постмортема. Эта статья — полная техническая разборка: что делают все 10 хендлеров bridge-admin контракта, почему атака возможна без приватных ключей валидаторов TAC L2 и какие конкретно адреса замешаны. Материал собран на основе декомпиляции TASM, восстановления c4-состояния и временно-узких сканов транзакций, поэтому каждый шаг атаки независимо проверяем on-chain.

TL;DR

  • TAC-мост на стороне TON состоит из bridge-admin контракта (2399 строк TASM), admin1 (874 строки), и ротатора (793 строки с двумя CHKSIGNU).
  • Bridge-admin не содержит ни одной on-chain инструкции CHKSIGN/CHKSIGNU/CHKSIGNS. Это подтверждается grep по всем 2399 строкам disassembly.
  • Drain-хендлер 0x0E50D313 принимает Merkle-proof, “виртуальный корень” из которого должен быть в словаре GLOBAL 5 (validators_dict). Если корень есть — мост релизит обёрнутые джеттоны по адресу из пейлоада. Это легитимный, штатный механизм.
  • Источник записей в validators_dict — только ротатор. Хендлер 0x23B05641 (EPOCH_ROTATE) принимает корень “as is”, без верификации подписей валидаторов TAC L2 в TVM.
  • Кто бы ни контролировал ротатор, может впрыснуть любой корень -> drain любого джеттона. Это архитектурная уязвимость, а не баг.
  • Атакующий синхронизирован с ротатором: окно “впрыск корня -> drain” — 2-7 секунд. Это автоматизированный pipeline.
  • Кошелёк-триггер атаки был легитимным пользователем моста с июля 2025 года. Используется тот же кастомный шаблон кода, что и у другого кошелька на 48 тыс. TON, — сильный сигнал инсайдерского rug.

Хронология

  • 2025-07-15 — кошелёк-триггер 0:7d628ee4… создан и пополнен 100 TON с FixedFloat (CEX без KYC). В течение 10 месяцев использует мост для нормальных кросс-чейн обменов. Это anti-attribution: к моменту drain кошелёк выглядит как обычный пользователь.
  • 2026-05-10 — финальный получатель джеттонов V5R1 0:4f1e15f2… пополнен 4 TON, опять же через FixedFloat. Источники фондирования у двух кошельков разные — компартментализация.
  • 2026-05-11 ~09:14 UTC — первая drain-транзакция. Ротатор впрыскивает корень, через 2-7 секунд прокси-контракт claim-ит его на хендлере 0x0E50D313, мост релизит обёрнутые джеттоны на адрес V5R1.
  • С 09:14 UTC и далее — ротатор продолжает впрыскивать корни каждые ~36 секунд. Drain-кошелёк претендует на drain-полезные корни в окне их 90-секундного TTL.
  • 2026-05-12 12:14 UTC — снапшот c4-состояния моста: в validators_dict живёт только 2 корня (старые вытеснены), общий вывод уже более 2.5M USD в обёрнутых джеттонах.
  • 2026-05-14 (сегодня) — официальной реакции TAC нет, admin1 хендлер 0x7EE5A6D0 (WIPE_VALIDATORS) не вызывался, обновления кода не было. Drain продолжается.

Что такое TAC-мост

TAC — это EVM-совместимый L2, который ассоциирован с TON. Мост между TON и TAC работает по классической lock-and-mint схеме:

  • Пользователь шлёт TON-актив (например, USDT-jetton) в bridge-admin контракт.
  • Мост блокирует или сжигает актив и эмитит событие.
  • Валидаторы TAC L2 подписывают событие.
  • На стороне TAC chain выпускается соответствующий wrapped-актив.

В обратную сторону — наоборот: пользователь сжигает wrapped-актив на TAC, валидаторы подписывают, и TON-сторона релизит оригинальный jetton по адресу пользователя. Именно эта обратная ветка (TAC -> TON) и эксплуатируется в текущей атаке.

Bridge-admin контракт на TON — 0:20a5698ef27054ae67ce3a57481264642c4c15f8821f540949b29858b309ee2f. Он управляет мастер-контрактами обёрнутых джеттонов (wUSDT, wWETH, wcbBTC, нативный TAC и др.). Любой релиз джеттона или минт TAC возможен только из этого admin-контракта.

Анатомия drain-хендлера 0x0E50D313

Все 10 хендлеров bridge-admin контракта декодированы. Drain-релиз сидит на op-коде 0x0E50D313 (десятичное 240177939) и устроен так. Псевдокод сжат, но соответствует строкам 513-574 дизассемблера:

IFJMPREF {
  ; AUTH 1: отправитель должен быть прокси,
  ; чей state_init выводится из тела сообщения
  NEWC ... STDICT STDICT STU 1 ENDC
  HASHCU
  CTOS SDEQ
  THROWIFNOT 71

  ; AUTH 2: virtual_root_hash из Merkle-proof
  ; должен быть ключом в validators_dict (GLOBAL 5)
  XCTOS DROP
  PUSHINT 8 SDSKIPFIRST
  LDU 256
  GETGLOB 5 PUSHPOW2 8 DICTUGET
  NULLSWAPIFNOT NIP
  THROWIFNOT 201

  ; PAYLOAD: словарь сумм джеттонов, адрес назначения, сумма TON
  DUP CTOS LDDICT SKIPDICT LDMSGADDR LDGRAMS DROP
  ; ... формируется 0xD7B9C06E (mint TAC) + 0x7817B330 (release jetton)
}

Двое “ворот” авторизации:

  1. AUTH 1 (proxy-self-hash). Отправитель должен соответствовать хэшу state_init, который реконструируется из тела самого сообщения. То есть атакующий должен прислать сообщение от адреса, чей state_init указан в этом же сообщении. Это легко: разворачивается прокси-контракт на лету в той же транзакции.
  2. AUTH 2 (root in validators_dict). Из Merkle-proof извлекается virtual_root_hash, и этот корень должен присутствовать в словаре GLOBAL 5.

Никаких проверок подписей валидаторов, кворумов или chain-of-custody до TAC L2 в drain-хендлере нет. Если корень в словаре — релиз состоится.

Что управляет словарём validators_dict

Хендлер 0x23B05641 (EPOCH_ROTATE), строки 1213-1291. Логика:

IFJMPREF {
  GETGLOB 3                ; адрес ротатора
  SDEQ THROWIFNOT 72       ; отправитель == GLOBAL 3
  NOW GETGLOB 9 GEQ
  THROWIFNOT 202           ; ротация только после дедлайна
  XCHG_0I s3
  LDU 256                  ; новый root_hash из тела
  LDU 48                   ; новый deadline
  ... обновление GLOBAL 7/8/9 ...
  ; Эвикция корней с deadline <= NOW - GLOBAL 4 * GLOBAL 10
  WHILE { DICTUMIN ... DICTUDEL }
  ; Вставка (root_hash -> 48-bit deadline)
  NEWC GETGLOB 9 STU 48 ENDC CTOS
  GETGLOB 5 PUXC s4 s-1 PUSHPOW2 8 DICTUSET
  SETGLOB 5
}

Это вся граница доверия. Что отправит ротатор — то и считается “подтверждённой валидаторами” записью на стороне TON. Никаких CHKSIGN внутри bridge-admin для проверки кросс-чейн подписей — нет вообще.

При этом в c4 моста живёт прелюбопытная конструкция:

  • GLOBAL 10 = 30 — длительность эпохи в секундах.
  • GLOBAL 4 = 3 — множитель TTL для корней. Корень валиден 3 * 30 = 90 секунд.
  • В validators_dict в живом снапшоте — всего 2 корня. Старые корни эвиктятся при каждой ротации.

Это объясняет, почему drain трудно увидеть в свежем сэмпле моста: использованный корень живёт максимум 90 секунд и тут же удаляется. На снапшоте 12 мая в словаре остались только два самых свежих корня — следов уже использованных drain-корней в текущем состоянии нет, они восстанавливаются только из истории транзакций.

Цепочка атаки end-to-end

Рабочий пример: drain-транзакция fd239f3189f89f63d271f80a35b93f6ba2ecb092ed1e8eccdcd54f963daa7431 от 2026-05-11 02:27:28 UTC. 14 внутренних транзакций.

Шагsrc -> dstopроль
1ext -> Highload 0:7d628ee…0x59b6416bвнешний подписанный вход
2Highload -> self0xae42e5a4обёртка кошелька
3Highload -> PROXY 0:e31a1fb4…0x3b6616c6деплой прокси (state_init в теле) + вызов
4PROXY -> bridge-admin0x0E50D313drain trigger
5bridge -> wUSDT master0x7817b330запрос release
6bridge -> TAC jetton master0xd7b9c06emint TAC атакующему
7wUSDT master -> wUSDT wallet0x0f8a7ea5внутренний burn (release)
8TAC master -> новый TAC wallet0x178d4519внутренний transfer (mint dest)
9bridge -> Highload0xd53276dbвозврат excess
10USDT chain -> финальный wallet 0:2ced3a3b…0x178d4519800k USD₮ доходят до V5R1
11-14уведомления + excessвозвраты Highload + V5R1

Прокси-контракт 0:e31a1fb4… — экзотическая cell-ссылка на библиотеку с хэшем E46FA11560C837D5226A87E1BEC85BB89BF960C0077C8E4354A747242CBA1B05. Эта библиотека предзаписана командой TAC и хранится в самом bridge-admin (ref 1 в c4). То есть прокси-механизм — штатный, заложенный самой командой моста: атакующий не выкатывает свой шеллкод, он использует тот же шаблон, что и легитимные пользователи.

Синхронизация с ротатором

Узкоокошечный скан истории транзакций bridge-admin в окне 02:25-02:30 UTC 11 мая показывает идеальную синхронизацию:

Время (UTC)ИсточникopСодержит целевой корень 21259ae9…
02:26:11ротатор0x23B05641первая инъекция (другой корень)
02:26:18 (+7s)прокси0x0E50D313drain по предыдущему корню
02:26:45ротатор0x23B05641новая инъекция
02:26:47 (+2s)прокси0x0E50D313drain
02:27:21ротатор0x23B05641корень 21259ae9… впрыснут (offset 25)
02:27:28 (+7s)прокси0x0E50D313drain клеймит этот же корень

Окно между впрыском и клеймом — 2-7 секунд. Никакой человек не вводит руками транзакции с такой задержкой. Это автоматический pipeline: что-то слушает ротатора в реальном времени и сразу пушит drain.

Корни проблемы

В TON-контракте моста нет логического бага. Цепочка доверия admin1 -> rotator -> validators_dict -> drain работает ровно так, как закодирована. Это значит, что уязвимость лежит архитектурно: одна-единственная сущность (rotator) может вставить произвольный Merkle-корень в словарь без верификации подписей валидаторов TAC L2 на стороне TON.

Возможны три первопричины:

(C) Insider-rug — наиболее вероятно (~60%)

  • Drain-кошелёк был легитимным пользователем моста 10 месяцев. Чтобы запустить drain, нужно глубоко знать формат Merkle-proof пейлоадов и механизм прокси-библиотеки.
  • Кошелёк использует кастомный шаблон кода 11acad79…, который встречается ещё у одного кошелька на 48 тыс. TON (~120 тыс. USD). Это паттерн внутренней раздачи — программа валидаторов, партнёрская программа или team-issued wallet.
  • Отсутствие CHKSIGN в TVM на стороне TON — сознательное архитектурное решение TAC-команды. То есть rotator у них с самого начала считается trusted oracle. Misuse trusted-роли — ровно тот failure-mode, что эта архитектура и допускает.
  • Ротатор работает в автоматическом режиме, выпускает новые корни каждые ~36 секунд часами подряд. Если бы это была паника-реакция на компромисс — поведение было бы другим: остановка ротатора, попытка WIPE_VALIDATORS, deploy патча.

(A) Компрометация ключей ротатора (~25%)

  • В коде ротатора есть две инструкции CHKSIGNU и signers_dict в c4. По форме это N-of-M мультисиг (порог проверяется через GTINT 3 THROWIF_SHORT 44, что указывает на 2-из-3 или 3-из-N).
  • Если ключи N подписантов утекли (фишинг, supply chain, компрометация инфраструктуры) — внешний атакующий может подавать корректно подписанные сообщения ротатору, и тот будет добросовестно их пробрасывать в bridge-admin.

(B) Логический баг в ротаторе (~15%)

  • 793 строки TASM ротатора с двумя CHKSIGNU и сложной логикой PUXC2/SDATASIZE/dict-of-pending. Возможны replay-атаки, signature malleability, dict-collision.
  • Линейный аудит ротатора пока не проведён — это open work.

Различить (A) / (B) / (C) можно через декодирование signers_dict ротатора и сопоставление публичных ключей подписантов с известными адресами кошельков TAC-команды (особенно с кошельками, использующими шаблон 11acad79…).

Где admin1 и почему он молчит

admin1 (0:c1af58471b74ea5da7cbc7020ae0831bfa0b96e47ccb491943153bee796f2bc3) обладает экстренными полномочиями, любое из которых остановило бы drain:

  • 0x7EE5A6D0 (WIPE_VALIDATORS) — обнуляет GLOBAL 5 (validators_dict) и сбрасывает GLOBAL 14. Drain-хендлер перестанет находить корни в словаре, AUTH 2 всегда будет выкидывать exception 201.
  • 0x20FAEC53 (UPGRADE_CODE) — SETCODE BLESS POPCTR c3. Полная замена кода контракта без таймлока. Можно одним патчем добавить реальную проверку подписей валидаторов.
  • 0x5CEC6BE0 (SET_ROTATOR) — переназначение ротатора на любой адрес. Если у admin1 есть резервный мультисиг с реальной верификацией — можно переключить пайплайн на него.
  • 0x581879BC -> 0x6A4FBE34 (двухшаговая ротация admin) — передача admin1 другому адресу.

Все четыре полномочия — без таймлока. Решение можно принять и провести за одну транзакцию. То, что в течение трёх суток после начала drain admin1 не вмешался, — само по себе аргумент в пользу insider-сценария.

Что это значит для пользователей TON

Прямой риск касается всех, у кого есть позиции в обёрнутых джеттонах TAC-моста: wUSDT, wWETH, wcbBTC, нативный TAC, BLUM на TAC, tsTON и др. Пока admin1 не вмешается:

  1. Выводите ликвидность из всех wrapped-TAC джеттонов сейчас. Если она ещё там есть.
  2. Не делайте новых депозитов в мост в обе стороны. Любые средства, которые попадут под управление bridge-admin, могут быть выведены атакующим в течение секунд после публикации новой эпохи.
  3. Не покупайте wrapped-TAC джеттоны на DEX. Они “обеспечены” контрактом моста, который дренируется в реальном времени.
  4. Проверяйте свои подключенные dApp через раздел “Connected apps” в Tonkeeper / MyTonWallet. Если у вас есть активные сессии с frontend-ами, которые используют мост, — отзовите их.

Косвенный риск — пулы ликвидности на STON.fi / DeDust с парами обёрнутых джеттонов TAC. Если LP-провайдеры выйдут массово — токены TAC-моста на DEX просто схлопнутся к нулю, утянув за собой пары с TON и USDT. Этот сценарий тоже стоит закладывать.

Уроки для других мостов TON

Уязвимость TAC — не редкая прихоть, а паттерн. Любой DIY-мост на TON, где есть “trusted oracle” / “trusted rotator” и нет on-chain верификации кросс-чейн подписей, имеет ту же поверхность атаки. Если вы пользуетесь или разрабатываете мост — вот чек-лист:

  1. Grep по disassembly на CHKSIGN/CHKSIGNU/CHKSIGNS. Если в bridge-admin нет ни одной инструкции проверки подписи — это красный флаг. Доверие к мосту полностью переезжает на off-chain процесс, который вы не видите.
  2. Сколько подписантов реально подписывают релиз? Если ответ “один адрес-ротатор” — это не мост, это custodian с дополнительными шагами.
  3. Есть ли у admin’а UPGRADE_CODE без таймлока? Если есть — это rug-кнопка. Даже если команда честная, утекший ключ admin = drain любой суммы в одной транзакции.
  4. Видны ли в коде хендлеры обнуления валидаторов? WIPE_VALIDATORS / SET_ROTATOR без таймлока — backdoor под кризис, но также backdoor под insider-rug.
  5. Прозрачен ли механизм ротации валидаторов? Если эпохи короткие (десятки секунд) и корни эвиктятся за минуты — следить за абьюзом извне практически невозможно. Это плюс к anonymity атакующего.

Disclosure-путь

Bug-bounty канал TAC — info@tac.build (cc tech@tac.build). Прежде чем слать им технические детали, проверяйте подлинность их канала: PGP-подпись от кошелька admin1, DKIM-валидация заголовков письма. Сцена сильно нагрелась, и атакующие могут пытаться вытянуть детали PoC под видом “официальных” представителей.

Repro на mainnet невозможен без ключей ротатора — TVM откатит транзакцию 0x23B05641 от любого другого отправителя на этапе compute (THROWIFNOT 72). Repro на testnet — стандартный путь: клонировать bridge-admin, назначить себя admin1 и rotator, продемонстрировать тот же drain. Этого достаточно для PoC без рисков.

Что дальше

Открытые задачи forensics-фронта:

  1. Декодировать signers_dict ротатора в c4 GLOBAL 4, сопоставить пабкеи подписантов с известными адресами TAC-команды и кошельками на шаблоне 11acad79…. Прямое совпадение — финальный аргумент в пользу insider-rug.
  2. Линейный аудит 793 строк TASM ротатора на signature-bug: replay, malleability, deduplication, off-by-one в проверке порога.
  3. Дизассемблер admin1 (874 строки). Отсутствие CHKSIGN у admin1 означает, что авторизация идёт каким-то другим путём — нужно найти, каким именно.
  4. Дизассемблер прокси-библиотеки по хэшу 0xE46FA11560… через прямой запрос к library_dict masterchain.
  5. Трекинг outflow от V5R1 (0:4f1e15f2…) — куда уходит 1.117M USD₮, через какие микшеры и в какой CEX.

Атака на TAC-мост — пример того, как уязвимость уровня архитектуры может прорастать в течение полугода, пока кошелёк-инсайдер ждёт момента. Текущая стадия — drain пока активен, официальной реакции нет. Если вы держите средства, ассоциированные с мостом TAC, — выходите сейчас.

Частые вопросы

Кросс-чейн мост между TON и EVM-сетями. Позволяет переносить активы (USDT, WETH, cbBTC и др.) между TON и совместимым L2 TAC. На стороне TON у моста есть admin-контракт, который выпускает обёрнутые джеттоны при депозите и сжигает их при выводе.
На момент анализа (12 мая 2026) — более 2.5 млн долларов в обёрнутых джеттонах (USDT, BLUM, tsTON и др.) плюс ~384 тыс. свежевыпущенных TAC. Drain продолжался на момент написания статьи: ротатор каждые ~36 секунд впрыскивает новые Merkle-корни, атакующий каждые 2-7 секунд их клеймит.
Кошелёк-триггер 0:7d628ee463b865b1507a69e8bc6644a9888d8e3c2fe491b879280a14aca5b4e1 (он же UQB9Yo7kY7hlsVB6aei8ZkSpiI2OPC_kkbh5KAoUrKW04ZxW). Конечный получатель джеттонов — V5R1 0:4f1e15f21bfbdbb0012707a61c70d86169573a2378b138273bbeb89a3b3a3800. Оба адреса смотрятся на любом TON-эксплорере (tonviewer, tonscan).
Логического бага в TON-контракте не найдено — вся цепочка доверия (admin1 -> rotator -> validators_dict -> drain) выполняется ровно как закодирована. Уязвимость архитектурная: в коде моста на TON нет ни одной инструкции CHKSIGN. Источник истины — то, что присылает rotator. Атакующий либо контролирует rotator, либо является самим TAC-team (insider-сценарий).
Категорически нет. Пока admin1 не вызвал WIPE_VALIDATORS (хендлер 0x7EE5A6D0) или не выпустил обновление кода с реальной проверкой подписей валидаторов TAC L2 — любые средства, оказавшиеся под управлением bridge-admin, могут быть выведены атакующим. Выводите ликвидность из всех обёрнутых TAC-джеттонов до публикации патча.
Прямо — нет. Это уязвимость конкретной архитектуры TAC, где rotator работает как trusted oracle без on-chain верификации подписей. Но шаблон проблемы (мост с trusted ротатором и без CHKSIGN в TVM) повторяется и в других DIY-мостах TON. Если у вас деньги в обёрнутых джеттонах нестандартных мостов — проверьте их disassembly на наличие CHKSIGN/CHKSIGNU.
Остановить drain может только admin1 (адрес 0:c1af58471b74ea5da7cbc7020ae0831bfa0b96e47ccb491943153bee796f2bc3). У него есть хендлеры WIPE_VALIDATORS (0x7EE5A6D0) и UPGRADE_CODE (0x20FAEC53), оба без таймлока. Тот факт, что admin1 в течение трёх суток не вмешался, тревожит сам по себе и подтверждает версию insider-rug.
На mainnet — нет, TVM откатит любую транзакцию 0x23B05641 от отправителя, не равного ротатору, на этапе compute (THROWIFNOT 72). В testnet — можно склонировать bridge-admin, назначить себя admin1+rotator и продемонстрировать тот же drain. Это легитимный PoC без рисков для mainnet-средств.

Похожие материалы