Back to Blog

~10,26 млн $: атаки Term Finance и MAYAChain | BlockSec

August 26, 2026
11 min read
Key Insights
  • На этой неделе в подборке представлены 2 значимых инцидента безопасности с совокупными убытками около $10,26 млн. Основной инцидент, Term Finance (~$8,5 млн), представлял собой захват управления в сети Ethereum, тогда как MAYAChain (~$1,76 млн) стал жертвой цепочки сбоев в учёте и валидации состояния в кросс-чейн сети на базе Cosmos-SDK.

  • Управление в каждом отдельном хранилище Term Finance основывалось на относительных порогах поддержки и участия без абсолютного минимума по капиталу или широте охвата; поскольку почти никто не обернул свои доли хранилища в токен управления, участие оказалось слишком незначительным, чтобы какое-либо противостоящее голосование могло провалить предложение. Злоумышленник получил квалифицированное большинство голосующей силы одного хранилища примерно за 0,5 ETH, а задержка исполнения лишь отсрочила результат — без какого-либо гаранта, способного вмешаться. Таким образом было захвачено шесть хранилищ Term на общую сумму около $8,5 млн.

  • В сети MAYAChain один специально сформированный депозит нарушил внутренний учёт цепочки, из-за чего действительные выводы средств были помечены как неудавшиеся, что запустило механизм восстановления, искусственно увеличивший зафиксированный баланс нативного токена в пуле с низкой ликвидностью без реального обеспечения; затем злоумышленник вывел это искусственно созданное значение, добавляя и выводя ликвидность.

За прошедшую неделю (17.08.2026 - 23.08.2026) в центре внимания оказались 2 значимых инцидента безопасности, с общими потерями примерно $10.26M.

Дата Инцидент Тип Оценочные потери
2026/08/18 MAYAChain Уязвимость в бизнес-логике ~$1.76M
2026/08/23 Term Finance Ошибочный дизайн управления ~$8.5M

Причины выбора

  • MAYAChain: Выбран, поскольку цепочка сбоев в учёте и проверке состояния позволила одному сформированному депозиту повредить исходящую сверку и завысить учтённый баланс пула с низкой ликвидностью без реального обеспечения, показывая, как несколько низкоуровневых дефектов, каждый из которых ограничен сам по себе, могут сложиться в вывод средств из кросс-чейн сети ликвидности.
  • Term Finance: Выбран, поскольку почти нулевая явка в управлении оставила отсутствие электората, способного отклонить вредоносное предложение, а задержка исполнения не была подкреплена ни хранителем (guardian), ни путём отмены, что позволило атакующему с минимальным капиталом доминировать в голосовании, принять предложение и вывести примерно $8.5M из шести хранилищ протокола.

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

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

Главное событие недели: Term Finance

Term Finance оказался в центре внимания на этой неделе, поскольку причиной сбоя стал не programming-баг, а изъян в дизайне управления — класс риска, который растёт по мере того, как протоколы запускают отдельный ончейн-DAO для каждого хранилища: когда почти никто не участвует в голосовании, контроль над голосованием можно купить дёшево, и собственное управление хранилища становится поверхностью атаки.

23 августа 2026 года Term Finance, протокол кредитования с фиксированной ставкой в Ethereum, потерял примерно $8.5M, когда атакующий захватил контроль над ончейн-управлением своих хранилищ. Поскольку практически никто из вкладчиков никогда не минтил токен управления хранилища, атакующий смог приобрести квалифицированное большинство голосующей силы примерно за 0.5 ETH, пройти проверки протокола на порог поддержки и минимальную явку, и исполнить вредоносное предложение, которое отменило стратегии хранилища и перевело его активы; таким образом было опустошено шесть хранилищ Term [1].

Контекст

Term Finance — протокол кредитования с фиксированной ставкой в Ethereum. Term Vaults — отдельный продукт, построенный над ним: каждое хранилище — это ERC-4626-хранилище, созданное на коде Yearn V3, где мета-хранилище принимает единый актив и распределяет его между набором стратегических хранилищ. Фреймворк Term разворачивает Aragon OSx DAO и контракт TokenVoting вместе с каждым хранилищем, и это DAO обладает полномочиями на обновление и роли в отношении хранилища, с которым оно поставляется. Таким образом хранилище с самого начала несёт собственную поверхность управления, независимо от того, кто его курирует и был ли в него вообще выделен какой-либо капитал.

Решения по управлению принимаются через TokenVoting. У каждого хранилища есть собственный токен доли и соответствующая обёртка управления Aragon GovernanceWrappedERC20; в анализируемом ETH Meta Vault это tmvETH и gtmvETH. Любой держатель токена доли может вызвать depositFor(), чтобы получить токен управления в соотношении один к одному, и любой держатель токена управления может делегировать (delegate()) полученные голоса. При создании предложения TokenVoting.createProposal() фиксирует snapshotBlock, supportThreshold и minVotingPower, рассчитанные на основе предложения токенов на момент этого снимка; голосующая сила затем считывается из обёртки на этом блоке.

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

Контракт управления, находящийся в центре этого инцидента — TokenVoting. Исполнение предложения ограждено только _canExecute(), которая для обычного (не досрочного) предложения требует, чтобы голосование ещё не было исполнено, чтобы предложение было закрыто, и чтобы прошли две проверки: isSupportThresholdReached() и isMinParticipationReached().

Обе проверки являются корректными реализациями голосования большинством, но каждая измеряет относительную долю, а не абсолютное значение. isSupportThresholdReached() требует лишь, чтобы голоса «за» превышали голоса «против» сверх заданного соотношения:

isMinParticipationReached() требует, чтобы отданные голоса достигли minVotingPower, которая сама рассчитывается как minParticipation * totalSupply на момент снимка:

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

Анализ атаки

Атакующий приобрёл контролирующую долю токена управления хранилища, а затем использовал её, чтобы принять и исполнить предложение, которое опустошило это хранилище; та же техника была применена к шести хранилищам Term. Следующий анализ прослеживает одно хранилище на основе транзакций 0xd354a1...d3014129 и 0x9f273f...44c2e8a0.

  • Шаг 1: Атакующий обменял 0.5 ETH на 0.485 tmvETH через форвардер Mayan Finance, который направил обмен и доставил доли хранилища атакующему.

  • Шаг 2: Атакующий обернул 0.485 tmvETH в 0.485 gtmvETH в соотношении один к одному, получив голосующую силу, использованную в следующих шагах.

  • Шаг 3: Атакующий вызвал propose(), которая считала собственную голосующую силу и вызвала TokenVoting.createProposal(). Контракт зафиксировал снимок, считав общее предложение токенов управления на этом блоке как всего 0.535 gtmvETH, так что доля атакующего составляла около 90.66% всего электората.

  • Шаг 4: Атакующий вызвал vote(), чтобы отдать всю свою голосующую силу за предложение, став единственным участником.

  • Шаг 5: После периода голосования атакующий вызвал executeProposal(). Барьер canExecute() проверил isSupportThresholdReached(): поскольку атакующий был единственным голосующим «за», доля поддержки значительно превышала порог в 50%. Затем он проверил isMinParticipationReached(): собственная голосующая сила атакующего сама по себе превышала minVotingPower. Обе проверки были пройдены.

  • Шаг 6: Затем было исполнено вредоносное предложение, которое вывело средства, выделенные хранилищем tmvETH в его стратегию, конвертировало их обратно в ликвидный WETH, удерживаемый хранилищем, и позволило атакующему вывести активы. Применённая к шести хранилищам Term, атака привела к общим потерям примерно $8.5M.

Заключение

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


Другие инциденты этой недели

MAYAChain

18 августа 2026 года MAYAChain, кросс-чейн сеть ликвидности на Cosmos-SDK, потеряла примерно $1.76M, когда единый сформированный депозит повредил внутренний учёт сети. Заставив действительные выводы выглядеть как неудавшиеся, депозит запустил путь восстановления, который завысил учтённый баланс нативного токена пула с низкой ликвидностью без реального обеспечения; атакующий затем вывел эту завышенную стоимость, добавив и вывев ликвидность [2]. Подтверждённые ончейн-исходящие потоки составили около $1.36M, в основном 20.83 BTC, а с учётом остаточных запасов нативного токена официальная оценка достигает примерно $1.76M.

Контекст

MAYAChain — кросс-чейн сеть ликвидности на Cosmos-SDK, чей нативный актив — CACAO. События на внешних цепях наблюдаются валидаторами и воспроизводятся в MAYAChain как наблюдённые транзакции. Когда входящее действие требует отправки активов вовне, узел планирует один или несколько TxOutItem и позже сверяет наблюдённые исходящие транзакции с этими запланированными записями. Все активы пула хранятся совместно в общих хранилищах Asgard; каждый пул ликвидности — это учётная позиция над этим общим хранением, а не отдельный баланс, и выводы оплачиваются из Asgard на основе учтённых балансов пула.

Торговые счета — это нативные учётные позиции MAYAChain для торговых активов, таких как ARB~ETH и ARB~LINK. Вывод с торгового счёта инициируется через нативный MsgDeposit с memo вида trade-:ARB~LINK. Хотя пользователь подписывает единую нативную транзакцию, обработчик депозита восстанавливает внутреннюю наблюдённую транзакцию и хранит ObservedTxVoter, привязанный к хешу нативной транзакции.

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

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

Во-первых, в handler_deposit.go каждое сообщение в нативной транзакции создаёт новый ObservedTxVoter, привязанный к хешу транзакции, и сохраняет его с помощью SetObservedTxInVoter():

txIn := ObservedTx{Tx: tx}
txInVoter := NewObservedTxVoter(txIn.Tx.ID, []ObservedTx{txIn})
txInVoter.Height = ctx.BlockHeight()
txInVoter.FinalisedHeight = ctx.BlockHeight()
txInVoter.Tx = txIn
h.mgr.Keeper().SetObservedTxInVoter(ctx, txInVoter)

Поскольку каждое сообщение в пакете использует один и тот же tx.ID, последующее сообщение перезаписывает состояние voter, записанное предыдущими, отбрасывая их метаданные планирования исходящих транзакций.

Во-вторых, в handler_common_outbound.go сверка исходящих транзакций начинается с voter.OutboundHeight (или voter.FinalisedHeight, когда первое равно нулю) и сканирует вперёд с шагом периода подписания:

outHeight := voter.OutboundHeight
if outHeight == 0 {
    outHeight = voter.FinalisedHeight
}
for height := outHeight; height <= ctx.BlockHeight(); height += signingTransPeriod {
    txOut, err = h.mgr.Keeper().GetTxOut(ctx, height)
    ...
}

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

В-третьих, в helpers.go путь восстановления оценивает «пропавший» актив как субсидию в CACAO, используя необработанную наблюдённую сумму, без ограничения относительно реальной глубины актива пула:

f.stolenAsset = f.stolenAsset.Add(coin.Amount)
runeValue := pool.AssetValueInRune(coin.Amount)
f.subsidiseRune = f.subsidiseRune.Add(runeValue)

В пуле, содержащем всего около 0.11 LINK глубины со стороны актива, эта неограниченная конвертация могла отобразить наблюдённую сумму LINK в огромную стоимость CACAO. Та же процедура затем фиксирует завышенный баланс пула в состояние до попытки перевода средств:

pool.BalanceCacao = pool.BalanceCacao.Add(f.subsidiseRune)
pool.BalanceAsset = common.SafeSub(pool.BalanceAsset, f.stolenAsset)
if err = mgr.Keeper().SetPool(ctx, pool); err != nil {
    ...
}

runeToAsgard := common.NewCoin(common.BaseAsset(), f.subsidiseRune)
if err = mgr.Keeper().SendFromModuleToModule(ctx, ReserveName, AsgardName, common.NewCoins(runeToAsgard)); err != nil {
    ctx.Logger().Error("fail to send subsidy from bond to asgard", "error", err)
    return err
}

Поскольку запись пула фиксируется до перевода, если перевод не может быть обеспечен, BalanceCacao пула никогда не откатывается. Наконец, в handler_observed_txout.go вызывающая сторона проглатывает возвращённую ошибку, отмечает voter как выполненный и продолжает работу, из-за чего противоречивое состояние пула может сохраниться:

_, err = handler(ctx, m)
if err != nil {
    ctx.Logger().Error("handler failed:", "error", err)
    slashObservedOutbound("failed_outbound")
    voter.SetDone()
    h.mgr.Keeper().SetObservedTxOutVoter(ctx, voter)
    continue
}

Анализ атаки

Атакующий скомпоновал эти дефекты в единой нативной транзакции, а затем извлёк стоимость из завышенного пула. Следующий анализ основан на транзакции MAYAChain 516BA14D...E9B7.

  • Шаг 1: Атакующий отправил нативную транзакцию 516BA14D...E9B7 с 23 сообщениями: 20 выводов trade-:ARB~ETH, 2 вывода trade-:ARB~LINK и финальный DONATE:ARB.LINK на одну базовую единицу.

  • Шаг 2: Торговые выводы создали действительные запланированные исходящие транзакции, но финальное сообщение DONATE перезаписало общий входящий voter для того же хеша нативной транзакции, стерев планирование исходящих транзакций предыдущих выводов.

  • Шаг 3: Когда исходящие транзакции ARB.LINK были позже наблюдены, сверка использовала поля высоты повреждённого voter'а и никогда не достигала блока, содержащего запланированные исходящие транзакции, поэтому она считала их пропавшими.

  • Шаг 4: Путь восстановления через штраф оценил пропавший LINK относительно тонкого пула ARB.LINK и завысил сторону CACAO пула примерно на 49.45M CACAO. Перевод из Reserve в Asgard, который должен был обеспечить субсидию, не удался, поскольку Reserve содержал лишь около 168K CACAO, что значительно меньше завышенной субсидии; но изменённый баланс пула сохранился, а обработчик с ошибкой был отмечен как выполненный.

  • Шаг 5: На более позднем блоке атакующий добавил ликвидность в искажённый пул с 100 CACAO и небольшим количеством LINK. Поскольку одна сторона пула была сильно выведена из пропорции, формула добавления ликвидности предоставила атакующему около 1T LP-единиц против приблизительно 731M существующих единиц.

  • Шаг 6: Атакующий немедленно вывел почти всю позицию, получив примерно 48.87M CACAO и 98.82 LINK, выплаченных из общих хранилищ Asgard на основе завышенного баланса пула, и обменял извлечённый CACAO через пулы MAYAChain на BTC и другие активы. Цена CACAO упала с примерно $0.115 до минимума около $0.013 во время распродажи.

48.87M CACAO, которые вывел атакующий, номинально стоили более $5M по докризисной цене, но не всё было реализовано в виде внешней выручки: значительная часть была обменена обратно в пулы, обрушив цену CACAO, и оппортунистические арбитражники, не связанные с атакующим, также извлекли стоимость во время обвала. Прямо подтверждённое ончейн-извлечение составило около $1.36M, в основном 20.83 BTC, а с учётом остаточных балансов CACAO и торговых счетов атакующего цифра достигает официальной оценки примерно $1.76M.

Заключение

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

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

Погрузитесь в транзакции, чтобы действовать разумно

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

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

Обнаруживайте каждую угрозу, оповещайте о важном и блокируйте атаки.

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

Источники

О BlockSec

BlockSec — это полностековый провайдер блокчейн-безопасности и криптовалютного комплаенса. Мы создаём продукты и услуги, которые помогают клиентам проводить аудит кода (включая смарт-контракты, блокчейны и кошельки), пресекать атаки в реальном времени, анализировать инциденты, отслеживать незаконные средства и выполнять обязательства по AML/CFT на протяжении всего жизненного цикла протоколов и платформ.

BlockSec опубликовал множество работ по блокчейн-безопасности на престижных конференциях, сообщил о нескольких атаках нулевого дня на DeFi-приложения, заблокировал многочисленные хакерские атаки, спасая более 20 миллионов долларов, и обезопасил миллиарды криптовалюты.

Sign up for the latest updates
Мост Harmony Cross-Shard ONE Mint + потеря ключей на ~$47М | BlockSec
Security Insights

Мост Harmony Cross-Shard ONE Mint + потеря ключей на ~$47М | BlockSec

10-16 авг 2026: 5 инцидентов, ~$47М убытков. Анализ — уязвимость Harmony: replay чеков между шардами создал 3.01Т ONE (не учтено в убытках). $47М — кражи ключей (Whale ~$25М, Kite ~$14М, Coinsbuy ~$7.9М) и ошибка Fox (~$117K).

~$1.6M потеряно: эксплойты токена Moke и LpdFi | BlockSec Weekly
Security Insights

~$1.6M потеряно: эксплойты токена Moke и LpdFi | BlockSec Weekly

За неделю 3-9 августа 2026 года в BNB Chain произошло 2 инцидента с общими потерями ~$1,6 млн из-за манипуляций с ценами. LpdFi (~$697K): атакующий использовал резервы PancakeSwap для завышения позиции. Moke Token (~$906K): манипуляция ценой и дублирование дивидендов LP.

Инцидент с COLDCARD: когда «случайная» сид-фраза кошелька оказалась не случайной
Security Insights

Инцидент с COLDCARD: когда «случайная» сид-фраза кошелька оказалась не случайной

Ошибка в прошивке COLDCARD направляла генерацию seed на слабый программный ГСЧ, делая кошельки восстановимыми офлайн. Обновление не устраняет проблему. К 7 августа 2026 г. подтверждённые потери — 1 405 BTC (~$91 млн), оценки до 2 055 BTC.