За прошедшую неделю (30.03.2026 - 05.04.2026) BlockSec обнаружила и проанализировала девять инцидентов атак с общими предполагаемыми убытками ~$287M. В таблице ниже приведена сводка этих инцидентов, а подробный анализ каждого случая представлен в следующих подразделах.
| Дата | Инцидент | Тип | Предполагаемые убытки |
|---|---|---|---|
| 2026/03/30 | Инцидент с неизвестным протоколом | Дефектная бизнес-логика | ~$10K |
| 2026/03/30 | Инцидент с токеном WDGG | Контроль доступа | ~$40K |
| 2026/03/31 | Инцидент с i6Token | Дефектная бизнес-логика | ~$273.8K |
| 2026/04/01 | Инцидент с Drift Protocol | Фишинговая атака | ~$285.3M |
| 2026/04/01 | Инцидент с LML Staking Protocol | Дефектная бизнес-логика | ~$950K |
| 2026/04/01 | Инцидент с Tactile | Манипуляция ценой | ~$12K |
| 2026/04/02 | Инцидент с токеном SAS | Дефектная бизнес-логика | ~$12K |
| 2026/04/03 | Инцидент Unknown-EIP-7702 | Контроль доступа | ~$17.2K |
| 2026/04/03 | Инцидент с Silo Finance | Ошибка конфигурации | ~$359K |
Лучший аудитор безопасности для Web3
Проверяйте дизайн, код и бизнес-логику перед запуском
1. Инцидент с неизвестным протоколом
Краткое описание
30 марта 2026 года неизвестный протокол в сети BNB Chain потерял ~$10K из-за дефектной бизнес-логики. Протокол выделял часть депозитов пользователей на покупку своего платформенного токена PSTART и добавление ликвидности, что означало, что пользователи фактически удерживали LP-позиции, подверженные колебаниям рыночной цены. Однако при выводе средств протокол не производил расчет на основе фактической выкупаемой стоимости LP на тот момент. Вместо этого он продолжал обещать фиксированную сумму стейблкоинов на основе исторического депозита и заранее установленных правил. В результате, после принудительного вложения капитала, атакующий смог вывести средства по заранее согласованной фиксированной стоимости, фактически перекладывая убытки, которые должна была нести LP-позиция, на протокол, в конечном итоге получив прибыль без затрат.
Предыстория
Протокол работает следующим образом: после того как пользователь вносит депозит BUSD, протокол автоматически использует часть средств для покупки PSTART, а затем объединяет их с оставшимися BUSD для добавления ликвидности в пул. Полученные LP-доли затем хранятся на кастодиальном хранении в Vault, а протокол фиксирует во внутреннем реестре заказ для пользователя, обещающий фиксированную ежедневную доходность.
После этого пользователь может получать вознаграждения по фиксированной процентной ставке, а при выходе Vault рассчитывает основную сумму и доходность согласно заранее установленным правилам, а не путем строгого выкупа на основе текущей реальной чистой стоимости активов, лежащих в основе LP-позиции.
Анализ уязвимости
Эта уязвимость проистекает из необоснованного дизайна протокола (0x587984...73a43c): логика расчетов оторвана от фактической стоимости базовых активов.
После того как пользователь вносит депозит BUSD, протокол использует часть средств для покупки PSTART и добавления ликвидности, что означает, что фактическая базовая позиция пользователя представляет собой экспозицию LP, стоимость которой колеблется вместе с рыночными ценами. Однако при выходе пользователя протокол не производит расчет на основе фактической выкупаемой стоимости LP на тот момент. Вместо этого он по-прежнему обещает выплатить фиксированную сумму стейблкоинов согласно историческому размеру депозита и заранее установленным правилам расчета.
Атакующий воспользовался этим недостатком, используя флэш-займ для получения большого объема капитала, а затем многократно выполняя deposit(). Тем самым атакующий вынуждал протокол непрерывно покупать PSTART и изменять состав активов в пуле, тем самым искусственно завышая цену PSTART и создавая арбитражную возможность.
Затем атакующий выполнил withdraw() и выкупил средства по заранее обещанной фиксированной стоимости, фактически перенеся убытки, которые должна была нести базовая LP-позиция, на сам протокол, в конечном итоге получив прибыль без затрат.
Анализ атаки
Следующий анализ основан на транзакции 0xf3b8...55e7.
-
Шаг 1: Атакующий получил примерно
2,000,000e18BUSDчерез флэш-займ и обменял их в пуле на19,013,120e18PSTART. -
Шаг 2: Атакующий многократно вызывал функцию
deposit()контракта для стейкинга средств. При каждом депозите протокол вычислял, сколькоBUSDследует использовать для покупки токенов, чтобы итоговое соотношение токен/BUSD, используемое для добавления ликвидности, более точно соответствовало текущему соотношению в пуле. Многократно внося депозиты, атакующий непрерывно вливалBUSDв пул, в то время как количествоPSTARTоставалось почти неизменным. В результате стоимостьPSTARTпродолжала расти. На этом этапеLP, полученная за счет депозитов атакующего, фактически приобреталась в убыток. -
Шаг 3: Затем атакующий продал
PSTART, купленный на Шаге 1, обратно в пул. Поскольку Шаг 2 увеличил резервыBUSDв пуле, этот обмен вернул примерно2,010,655e18BUSD, принеся прибыль около10,655 BUSDза один цикл атаки. -
Шаг 4: Наконец, атакующий выполнил
withdraw()для всех позиций стейкинга, открытых на Шаге 2. К этому моменту рыночная стоимость активов, соответствующих этим позициям, уже была намного ниже их первоначальной стоимости депозита. При нормальной экономической логике полный выкуп не должен был быть возможен. Однако протокол рассчитывал суммуBUSD, доступную для вывода, на основе исторического размера депозита, что позволило атакующему полностью вернуть все ранее вложенные принудительные инвестиционные затраты, не понеся никаких убытков.

Заключение
Первопричина этого инцидента заключается в том, что протокол инвестировал средства пользователей в LP-позиции, подверженные колебаниям рыночных цен, но при этом по-прежнему обещал расчет в фиксированной сумме BUSD на основе исторических значений депозитов и заранее установленных правил. Это создало разрыв между обязательствами протокола и реальной стоимостью его базовых активов.
Атакующий воспользовался этим недостатком, многократно используя deposit() для изменения структуры активов пула и завышения цены PSTART, затем завершил арбитраж через внешнюю позицию и, наконец, использовал withdraw() для выкупа ранее убыточных позиций по балансовой стоимости. Таким образом, убытки, которые должны были нести сами позиции, были перенесены на протокол, что в конечном итоге позволило получить безрисковую прибыль.
Начните работу с Phalcon Explorer
Погрузитесь в транзакции, чтобы действовать разумно
Попробовать бесплатно2. Инцидент с токеном WDGG
Краткое описание
30 марта 2026 года токен WDGG в сети BNB Chain подвергся эксплуатации, что привело к убыткам примерно в $40K. Первопричиной стало отсутствие контроля доступа в функции burnFrom(). В частности, функция burnFrom() позволяла любому пользователю сжигать токены WDGG с любого адреса. Атакующий воспользовался этим, сжигая токены WDGG из пула PancakeSwap, затем вызывая функцию sync() для уменьшения резервов WDGG в пуле, а впоследствии выполняя обратный обмен для извлечения прибыли.
Предыстория
Данный инцидент связан с единственным токеном WDGG, который является токеном с распределением дивидендов, взимающим комиссию с каждой транзакции.
Анализ уязвимости
Контракт токена WDGG (0x512de7...6b90c5) предоставлял burnFrom() без какой-либо авторизации вызывающей стороны. В результате любой адрес мог сжигать токены WDGG с любого держателя, включая сам пул PancakeSwap. В сочетании с публично вызываемой функцией sync() в PancakeSwap, которая приводит зафиксированные резервы в соответствие с фактическими балансами, этот недостаток позволял произвольно уменьшать резерв WDGG в пуле и нарушать инвариант постоянного произведения без какой-либо легитимной сделки, подвергая пул риску арбитража при ценовом дисбалансе.

Вторичный недостаток в функции setWdgAddress() позволял любому вызывающему помечать произвольный адрес как освобожденный от комиссии, что дополнительно максимизировало прибыль, извлекаемую через эту поверхность атаки.

Анализ атаки
Следующий анализ основан на транзакции 0x2da5...0bd1.
- Шаг 1: Атакующий сначала вызвал
setWdgAddress(), чтобы установить свой собственный адрес как освобожденный от комиссии, что позволило последующим переводам обходить комиссию за перевод токена.

-
Шаг 2: Атакующий затем обменял небольшое количество
BNBна токеныWDGGчерез пул PancakeSwap. -
Шаг 3: Получив
WDGG, атакующий вызвалburnFrom()для сжигания токеновWDGGнепосредственно с адреса пары PancakeSwap. -
Шаг 4: Затем атакующий вызвал
sync(), вынуждая пару обновить свои резервы в соответствии с манипулированными балансами токенов. В результате резервWDGGв пуле был уменьшен всего до 1 вея.

- Шаг 5: С сильно искаженными резервами пула атакующий выполнил обратный обмен и извлек прибыль из ценового дисбаланса.

Заключение
Данный инцидент в конечном итоге был вызван отсутствием контроля доступа в функции burnFrom() контракта токена WDGG. В результате атакующий смог сжигать токены непосредственно из пула PancakeSwap, манипулировать резервами пула через sync() и извлекать прибыль из возникшего ценового дисбаланса.
3. Инцидент с i6Token
Краткое описание
31 марта 2026 года токен i6 в сети BNB Chain потерял ~$273.8K, поскольку invest() изменял спотовую цену пула, в то время как withdraw() производил расчет балансов через устаревшую TWAP, и эти две функции могли быть скомпонованы в одной и той же транзакции. Атакующий завысил спотовую цену через invest(), выкупил избыточный i6 по устаревшей TWAP через withdraw() и продал i6 обратно в завышенный пул с прибылью.
Предыстория
Протокол работает следующим образом. Когда пользователь вызывает invest(), вносится депозит USDT, часть которого используется для покупки i6 на PancakeSwap. Приобретенный i6 вместе с оставшимся USDT затем добавляется в качестве ликвидности в пул USDT/i6, а полученные LP-токены сжигаются. Протокол фиксирует балансы пользователя и рефералов, номинированные в USDT.
При вызове withdraw() протокол вычисляет накопленную стоимость пользователя в терминах USDT и конвертирует ее в i6, используя цену TWAP, поддерживаемую протоколом (twapPrice).
Анализ уязвимости
Первопричина в контракте протокола (0x1cb36b...2a18a) заключается в том, что invest() изменяет спотовую цену пула USDT/i6 в качестве побочного эффекта покупки i6 и добавления ликвидности, в то время как withdraw() производит расчет накопленных балансов, номинированных в USDT, в i6, используя поддерживаемую протоколом TWAP (twapPrice), которая обновляется только после истечения временного окна. Ничто в контракте не предотвращает выполнение этих двух функций в одной и той же транзакции.
Поскольку вызов invest() может завысить спотовую цену в той же транзакции, в которой последующий вызов withdraw() считывает еще не обновленную TWAP, эти две функции фактически видят две разные цены для одного и того же состояния пула. Баланс, номинированный в USDT и выкупаемый через withdraw(), затем выплачивается в i6 по устаревшей, значительно более низкой цене, хотя каждый i6 теперь может быть реализован за гораздо большую сумму USDT в самом пуле. Этот разрыв превращает любой накопленный баланс USDT в протоколе в эксплуатируемый спред между внутренним расчетом и живым пулом.
Анализ атаки
Следующий анализ основан на транзакции 0xc1b9...2f16.
-
Шаг 1: Атакующий сначала получил
270,000 WBNBчерез флэш-займ, затем предоставил его в качестве обеспечения на Venus и заимствовал большое количествоUSDT. Атакующий также развернул атакующий контракт A по адресу0xda49и вспомогательный контракт B по адресу0x096a. -
Шаг 2: Атакующий контракт выполнил первый вызов
invest(). В процессе протокол сначала использовал531,489e18USDTдля покупки234,188e18i6, а затем добавил354,326e18USDTвместе с72,607e18i6в пул. В результате спотовая цена пула быстро выросла с примерно1.05159 USDT/i6до примерно4.89287 USDT/i6, в то время как зафиксированная протоколомTWAPпо-прежнему оставалась на уровне лишь1.05159.

-
Шаг 3: Затем атакующий перевел
124,014,184e18USDTна вспомогательный контракт B, который в свою очередь вызвалinvest()сreferrer = A. Этот шаг снова вынудил протокол выполнить массовую покупкуUSDT -> i6иaddLiquidity(), подтолкнув резервы пула к новому состоянию, соответствующему спотовой цене примерно15,528 USDT/i6. Однако, поскольку новое временное окно еще не истекло, протокол не обновилTWAPсоответствующим образом. -
Шаг 4: После завершения второго вызова
invest()атакующий контракт A, выступающий в качестве реферала, немедленно получил право на реферальное вознаграждение, номинированное вUSDT. Затем атакующий вызвалwithdraw(). Протокол использовал устаревшуюTWAPдля расчета суммыi6, подлежащей выплате, и перевел токены из собственного баланса, в результате чего общая выплата составила5,896,508e18i6. -
Шаг 5: Получив
i6, атакующий немедленно вызвалswapExactTokensForTokensSupportingFeeOnTransferTokens(), чтобы продать все5,896,508e18i6обратно в пул в обмен на125,177,224e18USDT. Поскольку эти токеныi6были рассчитаны по устаревшейTWAP, составлявшей около1.05159 USDT/i6, в то время как они были проданы против спотовой цены пула, которую атакующий подтолкнул до примерно15,528 USDT/i6, атакующий смог напрямую реализовать огромный спред между этими двумя ценами. -
Шаг 6: После погашения флэш-займа атакующий сохранил
273,802e18USDT, что и составило фактическую прибыль от атаки.
Заключение
Первопричина заключается в том, что функция, влияющая на спотовую цену (invest()), и функция, производящая расчет по TWAP (withdraw()), могли быть скомпонованы в рамках одной транзакции, позволяя им видеть разные цены для одного и того же состояния пула.
Чтобы предотвратить этот класс недостатков, протоколам, сочетающим взаимодействия с AMM с отложенным ценообразованием, следует избегать расчета балансов по TWAP в той же транзакции, в которой сопутствующая функция изменяет спотовую цену базового пула, и вместо этого привязывать выплаты к реальной реализуемой стоимости пула, а не к устаревшей или производной цене.
4. Инцидент с Drift Protocol
Краткое описание
1 апреля 2026 года (UTC) Drift Protocol в сети Solana был скомпрометирован на сумму примерно $285.3M. Первопричиной был не баг смарт-контракта, а сбой в процессе авторизации мультиподписи, усугубленный конфигурацией Security Council 2-из-5 с нулевым таймлоком и механизмом устойчивых nonce Solana, который позволял заранее собранным одобрениям мультиподписи оставаться действительными бесконечно долго, пока атакующий не решит их исполнить. После недель подготовки атакующий побудил двух из пяти подписантов заранее подписать вредоносные транзакции управления, привязанные к аккаунтам устойчивых nonce, впоследствии отправил их для захвата административного контроля, а затем ввел сфабрикованный залоговый актив (CVT), завысил его оракульную цену, ослабил лимиты вывода и вывел реальные активы через Drift Vault (JCNCMF...XJfrw).
Предыстория
Drift Protocol — это DeFi-протокол в сети Solana, поддерживающий маржинальную торговлю, кредитование, спотовые рынки и деривативы. Его высокопривилегированные операции, включая изменения администраторов, создание рынков, конфигурацию оракулов, обновление параметров риска и корректировку лимитов вывода, управляются через фреймворк мультиподписи Squads, а не через единый приватный ключ. На момент атаки Security Council Drift функционировал по конфигурации с порогом 2-из-5 и нулевым таймлоком, что означало, что любые два из пяти подписантов могли авторизовать административные действия с немедленным вступлением в силу. Безопасность этой системы зависит не только от хранения ключей подписантов, но и от целостности всего конвейера одобрения: какая транзакция была создана, что, по мнению подписантов, они одобряли, и совпадали ли итоговые исполненные инструкции с этим контекстом проверки.
Отдельно, аккаунты устойчивых nonce Solana заменяют короткоживущий blockhash на постоянный nonce, хранящийся в выделенном аккаунте, позволяя подписанной транзакции оставаться действительной бесконечно долго, пока nonce не будет продвинут. Этот механизм предназначен для легитимных случаев использования, таких как офлайн-подписание и отложенная отправка, но он вводит критический примитив атаки, разделяя момент подписания транзакции и момент ее исполнения в сети. Как только подписант одобряет транзакцию с устойчивым nonce, одобрение не может быть отозвано, если только владелец nonce вручную не продвинет аккаунт nonce.
Анализ уязвимости
Первопричина заключается не в баге смарт-контракта, а в трех структурных слабостях в конфигурации управления Drift, которые вместе превратили возможность социальной инженерии в потери $285.3M. Во-первых, устойчивые nonce устранили неявную сеть безопасности истечения срока действия подписи. При нормальных транзакциях на основе blockhash одобрение обманутого подписанта либо исполняется незамедлительно, либо безвредно истекает в узком временном окне, ограничивая масштаб скоординированной эксплуатации. Устойчивые nonce устраняют это ограничение: как только два подписанта Security Council были вынуждены одобрить вредоносные транзакции управления через вводящие в заблуждение запросы на подписание, их подписи оставались эксплуатируемыми бесконечно долго, давая атакующему полный контроль над временем исполнения.
Во-вторых, нулевой таймлок для административных действий означал, что как только заранее подписанные транзакции были отправлены, передача прав администратора вступала в силу немедленно, не оставляя окна для обнаружения или вмешательства. В-третьих, объем роли администратора был достаточно широк, чтобы создавать новые залоговые рынки, переключать источники оракулов и ослаблять лимиты вывода через единый привилегированный путь, поэтому одного успешного захвата управления было достаточно, чтобы превратить произвольный актив в фактическое извлечение средств без каких-либо дополнительных барьеров авторизации.
Анализ атаки
Атака развернулась в трех отдельных фазах: Предварительная подготовка, в ходе которой атакующий провел многонедельную операцию по созданию фиктивного залогового актива и получению доступа к управлению через вводящие в заблуждение запросы на подписание; Захват управления, в ходе которой две заранее подписанные транзакции с устойчивыми nonce были отправлены в быстрой последовательности для захвата административного контроля; и Извлечение средств, в ходе которой атакующий манипулировал параметрами протокола и вывел реальные активы через пути кредитования протокола. Диаграмма ниже иллюстрирует последовательность выполнения на этих трех фазах.
-
Фаза 1 (Предварительная подготовка): Начиная с 11 марта, атакующий вывел 10
ETHиз Tornado Cash и развернул CarbonVote Token (CVT), выпустив 750 миллионов единиц, затем разместил ликвидность на Raydium и использовал wash-трейдинг для формирования искусственной истории цен около $1. Параллельно, к 23 марта было создано четыре аккаунта устойчивых nonce, два из которых связаны с членами Security Council Drift, а два контролируются атакующим, что указывает на то, что по меньшей мере два из пяти подписантов уже подписали транзакции, привязанные к аккаунтам устойчивых nonce. 27 марта Drift провела запланированную миграцию Security Council из-за смены участника, аннулировав ранее собранные подписи; однако к 30 марта атакующий заново получил требуемый порог в новой конфигурации, демонстрируя активный мониторинг изменений в управлении сетью и адаптацию в реальном времени. -
Фаза 2 (Захват управления): 1 апреля примерно в 16:05 UTC, спустя около одной минуты после того, как Drift выполнила легитимный тестовый вывод средств из страхового фонда, атакующий отправил две заранее подписанные транзакции с устойчивыми nonce с интервалом в четыре слота. Первая транзакция (2HvMSg...2C4H) создала и одобрила вредоносное предложение о передаче прав администратора. Вторая транзакция (4BKBmA...RsN1) одобрила и исполнила его, начиная с
AdvanceNonceAccountдля активации сохраненного nonce, проходя черезproposalApproveиvaultTransactionExecute, и в конечном итоге вызвавUpdateAdminдля передачи административного контроля на адрес, контролируемый атакующим. -
Фаза 3 (Извлечение средств): Получив полные административные привилегии, атакующий создал залоговый рынок для
CVT, переключился на контролируемый им оракул для завышения его балансовой цены, а также повысил или полностью снял лимиты вывода на основных рынках активов. Затем атакующий внес большое количество завышенногоCVTв протокол и выполнил 31 быстрый вывод средств примерно за 12 минут, выведяUSDC,JLP,SOL,cbBTC,USDT,wETH,dSOL,WBTC,JTOиFARTCOINна общую сумму убытков примерно $285.3M, рассчитанную на основе счета вывода атакующего (HkGz4K...pZES).

Заключение
Первопричиной данного инцидента является не уязвимость смарт-контракта или компрометация ключа, а сбой в процессе авторизации мультиподписи в сочетании с отложенным исполнением на основе устойчивых nonce и административным путем с нулевым таймлоком. Заранее собранные одобрения, которые в норме истекли бы в течение нескольких минут, оставались бесконечно эксплуатируемыми, и как только они были отправлены, захват прав администратора и последующее извлечение средств не оставили окна для вмешательства.
Смягчение этого класса риска требует защиты всего конвейера авторизации, а не только хранения ключей подписантов, обеспечения таймлоков для высокопривилегированных операций и рассмотрения механизмов отложенного исполнения, таких как устойчивые nonce, как отдельной поверхности угрозы, требующей более высоких порогов подписи, ограниченных по времени или отзываемых одобрений, а также ограничений против бесконечно действительных подписанных транзакций.
5. Инцидент с LML Staking Protocol
Краткое описание
1 апреля 2026 года протокол стейкинга LML в сети BNB Chain был эксплуатирован на сумму примерно $950K. Первопричиной была несогласованность в логике расчета вознаграждений: конвертация вознаграждений считывала сохраненную цену LML/USDT, заблокированную периодом обновления в 3600 секунд, в то время как выплачиваемый LML мог быть выкуплен по живой цене AMM без какой-либо проверки отклонения между этими двумя значениями. Атакующий использовал флэш-займы для завышения спотовой цены пула и делегирование кода EIP-7702 для пакетного получения вознаграждений для 11 предварительно застейканных EOA-адресов в одной транзакции, получая непропорционально большое количество LML по устаревшей сохраненной цене и продавая его обратно через теперь сильно искаженный пул с прибылью.
Предыстория
LML — это протокол стейкинга в сети BNB Chain, где пользователи стейкают BNB через контракт APower для получения токенов LML в качестве вознаграждения. Вознаграждения вычисляются в терминах USDT, а затем конвертируются в суммы LML на основе сохраненной протоколом цены LML/USDT перед распределением. Сохраненная цена обновляется функцией updatePrice(), которая считывает спотовую цену AMM, но обеспечивает соблюдение периода охлаждения в 3600 секунд между обновлениями. Заявки на вознаграждение инициируются через receive() контракта APower на основе msg.sender, поэтому только исходный адрес стейкинга может получить свои собственные вознаграждения.
Анализ уязвимости
Основной недостаток в контракте стейкинга (0xbe9713...adce19) заключается в том, что начисление и погашение вознаграждений привязаны к двум разным ценам одного и того же пула без какой-либо проверки согласованности между ними. Формула вознаграждения reward += (10^18 * base_reward) / stored_price вычисляет выплату в LML на основе _prices[] — сохраненной протоколом цены LML/USDT, которая обновляется функцией updatePrice() только после периода охлаждения в 3600 секунд, однако выплачиваемый LML немедленно может быть выкуплен по живой спотовой цене AMM. Любая внешняя активность, изменяющая спотовую цену в течение этого периода охлаждения, открывает разрыв между замороженной ценой начисления и живой ценой продажи, и ничто в контракте не отклоняет заявку, когда этот разрыв становится необоснованным.



Второй структурный недостаток заключается в том, как пополняется источник вознаграждений. Когда баланса LML контракта PROOF недостаточно для выплаты заявки, функция swapBack() пополняет его, вызывая super._transfer(swapPair, PROOF, deficit) с последующим sync(), напрямую выводя LML из резервов LP-пары и приводя сохраненное состояние пары в соответствие, а не проходя через обычный обмен. Поскольку этот путь обходит механизм ценообразования пары, каждая заявка, запускающая его, уменьшает резервы LML пары и еще больше смещает спотовую цену без какого-либо компенсирующего притока токенов, поэтому повторяющиеся заявки механически увеличивают разрыв между замороженной сохраненной ценой и живой ценой продажи.


Анализ атаки
Следующий анализ основан на транзакциях 0x805d...5b47, 0x70f7...3572.
-
Шаг 1: Атакующий агрегировал большое количество
USDTиWBNBчерез флэш-займы от Moolah, заимствуя у Venus и Moolah Pool (используяWBNBв качестве обеспечения), а также флэш-займы от PancakeSwap V4, нескольких пулов V3 и пулов V2. Этот капитал был необходим для манипуляции ценой парыLML/USDT. -
Шаг 2: Атакующий вызвал
swapAndTrans()в контракте токенаLML, что обменяло накопленные в контракте комиссииLMLнаUSDT. Это истощило собственный балансLMLконтрактаLML, а это означало, что он больше не мог служить локальным источником токенов, и любое последующее распределение вознаграждений должно было бы забиратьLMLиз LP-пары черезswapBack().

- Шаг 3: Атакующий обменял
USDTнаLMLчерез PancakeRouterswapExactTokensForTokensSupportingFeeOnTransferTokens, при этом получателем был установлен мертвый адрес. Приобретенные токеныLMLбыли сожжены, а не получены атакующим. Единственной целью было истощитьLMLиз пары и завысить ценуLMLна AMM, атакующему не нужны были сами токены, только ценовое искажение.

- Шаг 4: Атакующий ранее внес депозиты в протокол стейкинга, используя 11 EOA-адресов. Поскольку
receive()контракта APower запускает_claimReward(msg.sender)на основеmsg.sender, вознаграждения могут быть получены только самим адресом, внесшим депозит. Чтобы получить вознаграждения для всех 11 EOA в рамках одной транзакции, атакующий использовал EIP-7702 для установки кода на этих EOA, позволяя вызывать их как контракты из основного контракта атакующего. Каждый EOA исполнял функциюtransfer(rst, fte), которая отправляла небольшое количествоBNBв APower, запускаяreceive(),_claimReward(EOA). Внутри каждой заявки:updatePrice()пропускался (период охлаждения 3600 секунд не был соблюден, поэтомуstored_priceоставалась на историческом низком уровне),updateUser()вычислял вознаграждение по устаревшей низкой цене,sendMining()переводилLMLиз PROOF в APower, а затем запускалswapBack(), который пополнял PROOF, забираяLMLиз LP-пары и вызываяsync(), дополнительно уменьшая резервы пары. Наконец,claimReward()распределялLMLна EOA, и каждый EOA переводил полученныйLMLобратно на контракт атакующего.


-
Шаг 5: Атакующий обменял накопленный
LMLобратно наUSDTчерез теперь сильно истощенный пул по чрезвычайно завышенной цене. -
Шаг 6: Погасил все флэш-займы, заимствования и комиссии. Перевел оставшуюся прибыль.
Заключение
Первопричина заключается в несоответствии между начислением и погашением вознаграждений в контракте стейкинга: выплаты рассчитываются на основе сохраненной цены LML/USDT, заблокированной периодом охлаждения в 3600 секунд, но рассчитываются в LML, реальная стоимость которого отслеживает живую цену AMM, без какой-либо проверки отклонения, связывающей эти два значения. Путь пополнения swapBack() усиливает этот недостаток, забирая LML непосредственно из LP-пары при каждой заявке, механически увеличивая разрыв между замороженной сохраненной ценой и живой ценой продажи по мере обработки большего числа заявок.
Протоколам стейкинга, которые рассчитывают вознаграждения на основе цен, полученных из AMM, следует обеспечивать проверку отклонения между сохраненной ценой и текущей спотовой ценой в момент заявки и отклонять транзакцию, когда разрыв превышает безопасный порог, а также избегать механизмов пополнения, которые изменяют резервы LP вне обычных путей обмена, поскольку такие механизмы обходят механизм ценообразования, который в противном случае ограничивал бы ущерб от устаревшего ценообразования.
6. Инцидент с Tactile
Краткое описание
1 апреля 2026 года Tactile, протокол многоуровневых депозитов в сети Polygon, понес убытки в размере ~$12K. Первопричиной была несогласованность в логике расчетов депозитов и выводов: как вход, так и выход конвертировались по текущей спотовой цене CES, а внутренняя доля не содержала никакой записи о стоимости актива, по которой она была изначально выпущена. Атакующий воспользовался этим, завышая спотовую цену перед внесением депозита для получения непропорционально больших долей, а затем занижая цену перед выводом средств для выкупа большего количества CES за долю, повторяя этот цикл через вспомогательные контракты для извлечения прибыли.
Предыстория
Tactile — это протокол многоуровневых депозитов в сети Polygon, где пользователи вносят CES в платежный контракт согласно выбранному уровню. Требуемая сумма депозита вычисляется на основе текущей спотовой цены CES и конфигурации уровня, и пользователю зачисляется внутренняя учетная доля, представляющая его позицию. При выводе средств зафиксированная доля конвертируется обратно в CES по спотовой цене на момент выхода, а не путем расчета по первоначально внесенной сумме, поэтому балансы пользователей фактически отслеживаются как ценозависимые учетные единицы, а не фиксированные суммы активов.
Анализ уязвимости
Основной недостаток в платежном контракте (0x9153e1...09b654) заключается в том, что депозит и вывод рассчитываются по одному и тому же оракулу спотовой цены в два разных момента времени без какой-либо неизменной привязки, связывающей их. В момент депозита контракт считывает getActualPrice() и конвертирует внесенный CES во внутреннюю долю на основе текущей цены; в момент вывода та же доля конвертируется обратно в CES с использованием спотовой цены на момент выкупа.
Поскольку сама доля не содержит записи о цене или сумме актива, по которой она была выпущена, любое изменение спотовой цены между этими двумя моментами напрямую превращается в несоответствие между внесенным и выведенным CES, делая учет протокола полностью зависимым от траектории цены, а не от стоимости базового актива.

Анализ атаки
Следующий анализ основан на транзакции 0xc321...da74.
- Шаг 1: Атакующий сначала занял
55,365e18CESиз флэш-пула Uniswap V3 и распределил средства по 5 вспомогательным контрактам. Каждый вспомогательный контракт сначала получил6,426e18CES, а затем вызвалdeposit(12). В процессе каждый вспомогательный контракт сначала запрашивалgetPriceForLevel(12), а затем подготавливал суммуCES, необходимую для этого депозита, на основе текущей цены.


- Шаг 2: После того как все 5 вспомогательных контрактов завершили первый раунд
deposit, атакующий выбросил300,000 CESнаDEX, снизив цену, используемуюbank, с1.067585до0.688542. Затем 5 вспомогательных контрактов по очереди выполнилиwithdraw, выкупая доли, созданные ранее по высокой цене, вCESпо теперь более низкой цене. Каждый вспомогательный контракт получил9,427e18CES.

-
Шаг 3: После завершения первого раунда
withdrawатакующий обменял полученный контрагентский актив обратно наCES, снова подтолкнув цену вверх. Затем атакующий повторил Шаги 2-3. -
Шаг 4: Наконец, после нескольких циклов атаки, и после погашения флэш-займа плюс комиссии за флэш-займ в размере
166.097975017841805126 CES, у атакующего осталось567,736e18CESв качестве прибыли.
Заключение
Первопричина данного инцидента заключается в том, что Tactile рассчитывал как депозит, так и вывод по манипулируемой спотовой цене, не привязывая учетную долю к сумме актива или цене на момент депозита, оставляя свой внутренний учет полностью подверженным любому движению цены между входом и выходом.
Протоколам, выпускающим долеподобные позиции против волатильного актива, следует привязывать каждую долю к конкретной сумме актива или цене на момент выпуска, чтобы погашение воспроизводило первоначальную экономическую стоимость, а не переоценивалось по живому оракулу. Там, где функции расчета должны ссылаться на текущую цену, им следует использовать средневзвешенные по времени или иным образом устойчивые к манипуляциям источники и избегать считывания той же спотовой цены, которая может быть изменена сделками, выполненными в той же транзакции, что и вызов расчета.
7. Инцидент с токеном SAS
Краткое описание
2 апреля 2026 года токен SAS в сети BNB Chain был эксплуатирован на сумму ~$12K. Первопричиной был недостаток в пользовательской логике перевода токена: отправка SAS в LP-пул только увеличивала глобальный счетчик sellBurn, и любой последующий обычный перевод мог затем сжечь SAS непосредственно из пула и вызвать sync() для перезаписи его резервов, полностью минуя логику обмена AMM. Атакующий воспользовался этим, накапливая sellBurn через продажи, запуская несвязанный обычный перевод для сжигания SAS из пула и снижения его резерва до 1 вея, а затем совершая обратный обмен оставшегося SAS с прибылью.
Предыстория
SAS — это дефляционный токен в сети BNB Chain с пользовательской логикой перевода, построенной поверх пула PancakeSwap V2. Функция transfer() различает два пути: путь продажи, запускаемый, когда получателем перевода является LP-пул, который увеличивает глобальный аккумулятор sellBurn на переведенную сумму; и путь обычного перевода, который, когда sellBurn не равен нулю, сжигает SAS непосредственно из LP-пула, а затем вызывает sync() для обновления его резервов до нового текущего баланса. Эти два пути предназначены для совместной работы как дефляционный механизм, уменьшающий резерв SAS в пуле в ответ на накопленное давление продаж.
Анализ уязвимости
Основной недостаток в контракте токена SAS (0xbfa266...3d91c6) заключается в том, как дефляционный механизм встроен в transfer(). Когда SAS отправляется в LP-пул, контракт увеличивает глобальный аккумулятор sellBurn на переведенную сумму. Затем при любом последующем обычном переводе, если sellBurn не равен нулю, контракт вызывает _burnFromPair() для сжигания SAS непосредственно из LP-пула и следует за этим вызовом sync(), который перезаписывает резервы пула в соответствии с его текущим балансом.
Проблема заключается в том, что этот путь сжигания и синхронизации перезаписывает резервы пула, минуя логику обмена AMM. Как только sellBurn был повышен переводами в пул, несвязанный обычный перевод достаточен для запуска сжигания и синхронизации, вынуждая резерв SAS в пуле стремиться к нулю и создавая крупное ценовое искажение, которое затем может быть извлечено через обычный обмен.



Анализ атаки
Следующий анализ основан на транзакции 0x878e...adc5.
-
Шаг 1: Атакующий занял
WBNBчерез флэш-займ. -
Шаг 2: Атакующий обменял
WBNBнаSAS.
-
Шаг 3: Атакующий развернул контракт и выполнил логику атаки в
constructor(). Это было использовано для обхода проверкиis_contract()протокола, поскольку контракт токена требовал, чтобы отправительtransfer()был либо адресом из белого списка, либо EOA.


- Шаг 4: Атакующий перевел
SASв пул, что запустило путь продажи и накопилоsellBurn.

- Шаг 5: Атакующий развернул второй контракт и снова, внутри его
constructor(), инициировал обычный перевод. ПосколькуsellBurnуже был не равен нулю после Шага 4, этот перевод запустил функцию_burnFromPair(), которая сожглаSASнепосредственно из LP-пула, а затем вызвала функциюsync(), уменьшив резервSASдо 1 вея.

- Шаг 6: Атакующий выполнил обратный обмен и продал оставшийся
SASс прибылью.
Заключение
Первопричина данного инцидента заключается в недостатке пользовательской логики перевода токена SAS: обычный перевод мог напрямую сжигать SAS из LP-пула и синхронизировать его резервы с уменьшенным балансом, позволяя превращать накопленную активность продаж в произвольное уменьшение резерва SAS в пуле, а оттуда — в крупное ценовое искажение, которое затем можно было извлечь через обычный обмен.
Токены не должны вмешиваться во внешний пул AMM и изменять его резервы вне обычных путей обмена. Если дефляционный дизайн действительно требует сжигания из пула, сжигание и сопутствующий вызов sync() должны быть привязаны к строго контролируемому внутреннему триггеру, например, доверенному хранителю или расписанию с ограниченной частотой, а не подключены к произвольным пользовательским переводам.
8. Инцидент Unknown-EIP-7702
Краткое описание
3 апреля 2026 года пользовательский аккаунт в сети BNB Chain, который включил делегированный код через EIP-7702, был опустошен на ~$17.2K. Делегированный код предоставлял функцию pancakeV3SwapCallback() без надлежащего контроля доступа. Атакующий напрямую вызвал этот колбэк с подготовленными данными вызова, вынудив аккаунт жертвы перевести свои токены на адрес, контролируемый атакующим.
Предыстория
EOA жертвы использовал транзакцию EIP-7702 типа 4 для установки делегированного кода, чтобы аккаунт мог выполнять логику, связанную с обменом. Однако делегированная реализация включала публичную функцию pancakeV3SwapCallback(), которая должна была вызываться только во время легитимного колбэка пула PancakeSwap V3.
Анализ уязвимости
Первопричина заключается в отсутствии контроля доступа в функции pancakeV3SwapCallback() делегированного контракта (0x02C809...aEDbAE).
В корректном дизайне колбэка UniswapV3/PancakeV3 колбэк должен проверять, что msg.sender является ожидаемым каноническим пулом (полученным из фабрики + пары токенов + комиссии, или проверенным против доверенного списка пулов). В данном случае эта проверка отсутствовала, поэтому любой внешний вызывающий мог вызвать колбэк напрямую.
Поскольку колбэк выполняет переводы токенов из аккаунта, в котором он размещен, отсутствие проверки msg.sender означает, что любой внешний вызов с положительными amount0Delta/amount1Delta попадает в путь оплаты внутри колбэка и выводит токены из аккаунта жертвы, без фактического совершения обмена.
Анализ атаки
Следующий анализ основан на транзакции 0x5b2c...4261.
- Шаг 1: Атакующий напрямую вызвал
pancakeV3SwapCallback()с подготовленными параметрами, запустив логику перевода в колбэке для вывода токенов жертвы на адреса, контролируемые атакующим.


Заключение
Данный инцидент был вызван развертыванием логики колбэка обмена на аккаунт EIP-7702 без строгой аутентификации колбэка. Поскольку функции pancakeV3SwapCallback() не хватало контроля доступа, колбэк мог быть вызван любым внешним вызывающим и использован для вывода токенов из аккаунта жертвы без фактического совершения легитимного обмена.
Для любого контракта или делегированного кода EIP-7702, реализующего колбэки в стиле V3, разработчики должны проверять, что msg.sender является каноническим пулом PancakeV3 (полученным из доверенной фабрики, пары токенов и уровня комиссии), прежде чем колбэк перейдет к какой-либо логике перевода.
9. Инцидент с Silo Finance
Краткое описание
3 апреля 2026 года хранилище soUSDC протокола Silo Finance в сети Arbitrum было эксплуатировано на сумму примерно $359K. Первопричиной стало совпадение трех дефектов: неизменяемого оракула wstUSR, который по-прежнему оценивал токен на уровне ~1.133 даже после того, как его рыночная цена обвалилась до ~0.12, механизма лимита предложения на soUSDC, который ограничивал только собственные депозиты хранилища, но не внешние, и недостатка учета totalAssets(), который учитывал внешне зачисленные доли bUSDC без выпуска соответствующих долей soUSDC. Внося USDC напрямую на рынок wstUSR с нулевым лимитом с параметром receiver=soUSDC, атакующий завысил цену доли хранилища, занял тот же USDC обратно под завышенное залоговое обеспечение wstUSR и выкупил ранее приобретенные доли soUSDC по завышенной оценке, а недостача была покрыта из других здоровых рынков в очереди на вывод.
Предыстория
Silo Finance — это протокол кредитования с изолированным риском в сети Arbitrum. Каждый рынок Silo представляет собой двустороннюю кредитную пару (например, wstUSR/USDC): заемщики вносят залог (wstUSR) и заимствуют кредитный актив (USDC), в то время как кредиторы вносят USDC и зарабатывают проценты. Когда кредитор вносит USDC на определенный рынок, он получает токен доли депозита этого рынка. Когда заемщик вносит залог, он получает токен доли залога (например, bwstUSR-149).
soUSDC — это SiloVault, который находится поверх нескольких рынков Silo для агрегации кредитования USDC. Пользователи вносят USDC в soUSDC и получают доли soUSDC. Затем хранилище направляет внесенный USDC на одобренные рынки Silo на основе лимитов предложения, установленных аллокатором, и само хранилище хранит доли bUSDC каждого рынка, в который оно внесло депозит. Когда пользователь выкупает доли soUSDC, хранилище рассчитывает, сколько USDC стоят эти доли, используя totalAssets(), которая перебирает каждый рынок в очереди на вывод и суммирует баланс долей bUSDC хранилища на каждом из них. Затем хранилище выводит USDC из своих базовых рынков для выплаты выкупающему.
wstUSR (Wrapped Staked USR) — это стейкинг-дериватив стейблкоина USR, выпущенного Resolv. После того как Resolv была эксплуатирована, USR потерял привязку, stUSR также потерял привязку, а вторичная рыночная цена wstUSR обвалилась до ~0.12. Однако Chainlink-фид для wstUSR отслеживал только обменный курс wstUSR/stUSR (~1.133), а протокол неявно предполагал, что 1 stUSR = 1 USD, поэтому оракул продолжал оценивать wstUSR на уровне ~1.133, что представляло собой разрыв примерно в 10 раз по сравнению с рыночной реальностью.
Анализ уязвимости
Адрес оракула рынка wstUSR/USDC жестко закодирован как неизменяемый в SiloConfig и не может быть заменен. Сам контракт оракула (0x836a1a...04425e) представляет собой ChainlinkV3Oracle, чей базовый Chainlink-фид (описание: «wstUSR / stUSR Exchange Rate») отслеживает только коэффициент обертывания wstUSR в stUSR (~1.133), а не цену на вторичном рынке. Протокол неявно предполагает, что 1 stUSR = 1 USD, поэтому он оценивает wstUSR на уровне ~1.133. После того как USR потерял привязку, stUSR также потерял привязку, и wstUSR обвалился до ~0.12 на открытом рынке, но оракул продолжал сообщать ~1.133, что представляло собой примерно 10-кратную переоценку.


Протокол частично осознавал риск: лимит предложения soUSDC для рынка wstUSR был установлен на 0, что означало, что хранилище никогда не направит USDC туда добровольно. Однако этот лимит регулирует только собственные исходящие вызовы deposit() хранилища. Поскольку wstUSR_Market.deposit() принимает произвольный параметр receiver, любой может внести USDC напрямую на рынок wstUSR и зачислить полученные доли bUSDC на адрес soUSDC, полностью обходя лимит предложения.
Это создает основной путь эксплуатации. Когда доли bUSDC попадают на баланс soUSDC через такой внешний депозит, totalAssets() учитывает их: она перебирает каждый рынок в очереди на вывод и считывает фактический баланс долей хранилища, без какой-либо проверки того, была ли позиция добровольно открыта. Между тем, для этих внешне зачисленных позиций не выпускаются новые доли soUSDC, поскольку собственная логика выпуска хранилища никогда не вызывалась. Результатом является то, что totalAssets увеличивается, а totalShares остается прежним, завышая цену доли soUSDC.




Анализ атаки
Следующий анализ основан на транзакции 0xf77a...f3e1.
Атакующий сначала купил wstUSR на вторичном рынке по цене ~0.12 за токен.
-
Шаг 1: Флэш-займ ~4,236,352
USDCот Morpho. Одной лишь неверной оценки оракула недостаточно — рынокwstUSRимел нулевую ликвидностьUSDC(лимит=0,soUSDCникогда туда не депонировал), поэтому не было ничего, под что можно было бы занять против завышенного залога. Флэш-займ предоставляет капитал, необходимый для последующих шагов депозита и «пожертвования». -
Шаг 2: Внесение
wstUSRна рынокwstUSRв качестве залога и получениеbwstUSR-149. Это подготовка к заимствованию на Шаге 5 — оракул оценивает 13,797wstUSRпримерно в 15,633 (по 1.133 каждый), хотя атакующий заплатил лишь ~1,656.

- Шаг 3: Внесение ~4,222,007
USDCв хранилищеsoUSDC, получение долейsoUSDC(~91.5% от общего предложения). Хранилище направляет этотUSDCна существующие здоровые рынки (не на рынокwstUSR, поскольку лимит=0). Эти долиsoUSDCявляются инструментом для извлечения прибыли на Шаге 6 — чем больше долей держит атакующий, тем больше он выигрывает при завышении цены доли.

- Шаг 4: Внесение ~14,344
USDCнапрямую на рынокwstUSRчерезwstUSR_Market.deposit(receiver=soUSDC)и выпускbUSDC-149наsoUSDC. Полученные долиbUSDCзачисляются на адресsoUSDC, а не на адрес атакующего. Это ключевая манипуляция:totalAssets()soUSDCтеперь включает эти долиbUSDCпо номинальной стоимости (~14,344USDC), но новые долиsoUSDCне выпускаются, поскольку собственная логика депозита хранилища никогда не вызывалась —totalAssetsрастет,totalSharesостается прежним, и цена долиsoUSDCзавышается. Одновременно это создает ликвидностьUSDCна ранее пустом рынкеwstUSR, что необходимо для следующего шага.

- Шаг 5: Заимствование ~14,344
USDCс рынкаwstUSRс использованием залога, внесенного на Шаге 2. Оракул оценивает залог примерно в 15,633, поэтому при максимальном LTV 92% атакующий может занять ~14,344. Это возвращаетUSDC, «пожертвованный» на Шаге 4 — заимствование и «пожертвование» нейтральны по денежному потоку. Но рынокwstUSRтеперь полностью истощен: весьUSDCбыл заимствован, оставив лишь непогашенный кредит, обеспеченный почти бесценным залогомwstUSR.soUSDCпо-прежнему держит долиbUSDCпо номинальной стоимости вtotalAssets().

- Шаг 6: Выкуп всех долей
soUSDC, приобретенных на Шаге 3. Цена доли теперь завышена после «пожертвования» на Шаге 4, поэтому атакующий получает ~4,235,143USDC, что примерно на 13,136 больше, чем 4,222,007, внесенных на Шаге 3. Хранилище пытается вывести средства с рынкаwstUSR, но обнаруживает нулевую ликвидность (заимствовано на Шаге 5), поэтому оно забирает недостачу с других здоровых рынков в очереди на вывод. Именно здесь материализуются убытки: реальныйUSDCс рынков других вкладчиковsoUSDCпереводится для покрытия завышенного выкупа.



- Шаг 7: Погашение флэш-займа.
После 32 циклов soUSDC остается держателем позиций bUSDC на рынке wstUSR, номинально оцененных в ~359K, но обеспеченных залогом wstUSR, стоящим лишь малую долю от этой суммы — 100% использование, фактически невосстановимый плохой долг, ложащийся на оставшихся вкладчиков soUSDC.
Заключение
Данный инцидент был вызван совпадением оракула, отслеживающего курс стейкинг-обмена, а не рыночную цену обесценившегося актива, системы учета хранилища, которая учитывала внешне зачисленные позиции в totalAssets() без выпуска соответствующих долей, и механизма лимита предложения, который ограничивал только собственные депозиты хранилища, но не внешние. Неизменяемость адреса оракула в SiloConfig предотвратила любое экстренное исправление после того, как проблема стала очевидной.
Протоколам хранилищ, агрегирующим кредитные рынки, следует обеспечить, чтобы totalAssets() учитывала только позиции, в которые хранилище вошло через свои собственные операции депозита, а не внешне зачисленные балансы долей. Адреса оракулов не должны быть постоянно неизменяемыми; должны существовать механизмы экстренного управления для обновления ценовых фидов, когда базовые активы теряют привязку.
О BlockSec
BlockSec — это полностековый поставщик услуг блокчейн-безопасности и криптовалютного комплаенса. Мы создаем продукты и услуги, которые помогают клиентам проводить аудит кода (включая смарт-контракты, блокчейн и кошельки), пресекать атаки в реальном времени, анализировать инциденты, отслеживать незаконные средства и выполнять обязательства AML/CFT на протяжении всего жизненного цикла протоколов и платформ.
BlockSec опубликовала несколько статей по блокчейн-безопасности на престижных конференциях, сообщила о нескольких атаках нулевого дня на DeFi-приложения, заблокировала несколько взломов, спасая более 20 миллионов долларов, и обеспечила безопасность криптовалют на миллиарды долларов.
-
Официальный сайт: https://blocksec.com/
-
Официальный аккаунт в Twitter: https://twitter.com/BlockSecTeam



