Back to Blog

Потеряно $7,04 млн: GiddyDefi, Volo Vault и другие | Еженедельный обзор BlockSec

Code Auditing
April 29, 2026
18 min read
Key Insights

За прошедшую неделю (20.04.2026 — 26.04.2026) компания BlockSec обнаружила и проанализировала восемь инцидентов, связанных с хакерскими атаками. Общий ориентировочный ущерб составил ~$7,04 млн. В таблице ниже приведена сводка этих инцидентов, а подробный анализ каждого случая представлен в следующих подразделах.

Дата Инцидент Тип Ущерб (оценка)
2026/04/19* Контракт Custom Rebalancer Arbitrary Call (Произвольный вызов) ~$64K
2026/04/20 REVLoans (Juicebox) Некорректная проверка ~$50.7K
2026/04/22 Volo Vault / Navi Компрометация ключа ~$3.5M
2026/04/22 Kipseli Router Некорректная проверка ~$72.35K
2026/04/23 GiddyDefi Неполная проверка подписи ~$1.3M
2026/04/25 Purrlend Компрометация ключа ~$1.5M
2026/04/26 SingularityFinance Неверная настройка оракула ~$413K
2026/04/26 Scallop Ошибка учета ~$142.7K

*Инцидент с кастомным контрактом ребалансировки не был освещен в отчете на прошлой неделе и включен сюда для полноты картины.

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

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

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

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

Злоумышленник не взламывал подпись, не использовал флэш-займы и не манипулировал ценами. Он просто повторно использовал легитимную подпись, заменив подписанные поля на данные своего контракта. «Использование вашей собственной подписи против вас» — это самое наглядное доказательство того, как частичное покрытие EIP-712 превращает валидную подпись в универсальный инструмент для несанкционированного доступа.

23 апреля 2026 года контракт GiddyVaultV3 в сети Ethereum подвергся атаке с ущербом примерно в $1,3 млн. Схема проверки подписи покрывала только SwapInfo.data, оставляя поля aggregator, fromToken, toToken и amount вне хеша EIP-712. Это позволило повторно использовать валидную подпись с подменой этих полей. Злоумышленник указал в качестве aggregator вредоносный контракт, а fromToken — LP-токен стратегии, что привело к выводу активов на сумму около $1,3 млн.

Предыстория

GiddyVaultV3 (0x5f0a...4318) — это хранилище (vault) для доходного фермерства, где пользователи вносят и выводят средства через deposit() и withdraw(). Каждая операция должна содержать структуру авторизации VaultAuth, подписанную бэкендом. Она включает EIP-712 подпись и массив SwapInfo[ ], описывающий маршруты обмена токенов. При выполнении обмена контракт вызывает GiddyLibraryV3.executeSwap(), который выполняет forceApprove для swap.fromToken, предоставляя разрешение swap.aggregator, а затем проводит обмен через aggregator.call(swap.data). Контракт стратегии управляет средствами в соответствии с настроенной логикой.

EIP-712 — это стандарт для подписи структурированных данных вне сети (off-chain): протокол, использующий подпись, воссоздает ту же структуру в блокчейне, хеширует её с заданной подписью домена (domain separator) и восстанавливает адрес подписавшего. Безопасность любого потока EIP-712 зависит от того, покрывает ли хеш каждое поле, влияющее на выполнение. В дизайне Giddy бэкенд подписывает VaultAuth, содержащий намерение пользователя и маршруты обмена. Функция _validateAuthorization() восстанавливает эту структуру перед тем, как стратегии будет разрешено перемещать средства.

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

Уязвимость кроется в функции _validateAuthorization() контракта GiddyVaultV3. При построении подписанной полезной нагрузки хешируется только поле data каждого SwapInfo; поля aggregator, fromToken, toToken и amount исключены из подписи. Это означает, что любой владелец валидной подписи может свободно подменять оставшиеся поля SwapInfo, при этом проверка подписи будет проходить успешно.

Каждое исключенное поле — это отдельный рычаг: aggregator становится и тем, кто тратит средства, и целью вызова через forceApprove и aggregator.call(swap.data); fromToken выбирает, какой актив стратегии будет одобрен; amount устанавливает потолок суммы; toToken лишь проходит проверку returnAmount > 0. Подписанные данные data не ограничивают ничего из этого, поскольку ни одна из этих целей внутри них не упоминается.

Анализ атаки

Приведенный ниже анализ основан на транзакции 0x5edb66...5482e5.

  • Шаг 1: Злоумышленник получил из блокчейна легитимную подпись VaultAuth, авторизованную бэкендом, сохранив поле data без изменений. Поскольку каждый вызов deposit() или withdraw() транслирует полную полезную нагрузку VaultAuth в сеть, любая историческая транзакция стала бесплатным источником пригодной для использования подписи. Злоумышленнику потребовалась лишь та, чье поле data подходило для запланированного вызова обмена.

  • Шаг 2: Используя полученную подпись, злоумышленник оставил значения signature, nonce и data без изменений, но подменил остальные поля. fromToken был установлен на LP-токен, которым владел контракт стратегии (реальный актив), поэтому forceApprove предоставил разрешение на токен, принадлежащий протоколу. aggregator был заменен на вредоносный контракт злоумышленника, поэтому и одобрение, и последующий aggregator.call() были направлены на код, контролируемый атакующим. Поскольку эти поля находились вне области проверки подписи, _validateAuthorization() приняла измененную структуру. Чтобы обойти финальную проверку require(returnAmount > 0, "SWAP_NO_TOKENS_RECEIVED"), вредоносный агрегатор реализовал функцию минтинга (выпуска), которая возвращала протоколу поддельные токены. Это обеспечило успешное выполнение проверки без реального обмена.

  • Шаг 3: Поскольку вредоносный агрегатор получил одобрение на предыдущем шаге, злоумышленник вызвал transferFrom, чтобы переместить LP-токены хранилища напрямую во вредоносный агрегатор, завершив кражу. Этот шаг полностью находился вне защищенного пути выполнения протокола. К моменту возврата из executeSwap() разрешение уже было записано, а проверка баланса после вызова — пройдена, поэтому у протокола не было возможности вмешаться.

Заключение

Первопричиной этой атаки стала неполная проверка подписи EIP-712. Основные поля SwapInfo, напрямую управляющие движением средств, остались незащищенными, что позволило злоумышленнику подменить маршрут обмена и адрес агрегатора, предъявив при этом валидную подпись. Разработчикам, интегрирующим внешние агрегаторы, следует:

  • Убедиться, что подписи EIP-712 покрывают все поля, влияющие на исход выполнения, включая aggregator, fromToken, toToken и amount.

  • Применять «белый список» (whitelist) агрегаторов, чтобы предотвратить вызовы непроверенных внешних контрактов.

  • Ограничивать toToken только ожидаемыми базовыми токенами, чтобы предотвратить обход проверок баланса с помощью фейковых активов.

В более широком смысле, EIP-712 в любой архитектуре «разрешение-затем-вызов» должен хешировать каждое поле, влияющее на итоговое состояние сети, а не только намерение пользователя. В ситуациях, когда бэкенд-подпись является единственным барьером между параметрами, вводимыми пользователем, и привилегированным действием контракта, каждый параметр, поступающий в это действие (цель вызова, актив, сумма, получатель), должен находиться внутри подписанной структуры. Отношение к полю data как к подмене идентификатора вызова — это категориальная ошибка: идентификатор вызова — это кортеж всех его параметров, и любой параметр, оставленный вне подписи, по определению контролируется тем, кто отправляет транзакцию.

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

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


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


Контракт Custom Rebalancer

19 апреля 2026 года контракт ребалансировки sAVAX в сети Avalanche был взломан, что привело к выводу около $64 тыс. (~7 000 WAVAX) из кредитной делегации Aave V3 пользователя. Публичная функция выполняла произвольный target.call(data), сохраняя при этом делегацию пользователя, что позволило злоумышленнику вызвать borrow() в Aave с параметром onBehalfOf, установленным на жертву. Whitehat-бот обнаружил попытку взлома и вернул средства до того, как они были выведены.

Предыстория

Контракт ребалансировки (0x7a7b...a8c9) предоставляет функцию b2a13230(), предназначенную для ребалансировки позиции пользователя с плечом на Aave. Функция работает от имени пользователя через кредитную делегацию Aave V3: пользователь дает разрешение на заимствование, а ребалансер комбинирует эти кредиты с собственными средствами пользователя для корректировки позиции.

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

Первопричина заключается в том, что b2a13230() включает шаг target.call(data), а цель (target) и данные вызова (calldata) контролируются вызывающим лицом. Этот вызов выполняется, пока контракт всё еще действует в рамках кредитной делегации пользователя Aave V3. Таким образом, любая логика, вызванная на этом этапе, наследует право пользователя на заимствование. Нет белого списка разрешенных целей и нет ограничений на структуру данных вызова, поэтому вызов может обратиться к любому методу контракта, включая borrow() у Aave с onBehalfOf, установленным на пользователя.

Анализ атаки

Анализ основан на транзакции: 0xaaa1b2...35001b.

  • Шаг 1: Злоумышленник взял флэш-заем в sAVAX и USDC. Затем внес заемный USDC в Aave V3 через контракт ребалансера, чтобы обеспечить достаточное обеспечение для заимствования. Тем временем заемный sAVAX был переведен непосредственно на контракт ребалансера для подготовки к последующему шагу пополнения после заимствования.

  • Шаг 2: Злоумышленник снова вызвал функцию b2a13230(). Сначала функция выполнила обычную операцию заимствования, а затем дошла до секции произвольного вызова. На этом этапе злоумышленник сформировал вызов для прямого обращения к borrow() в Aave V3 с параметром onBehalfOf, установленным на адрес жертвы. Поскольку жертва предоставила кредитную делегацию контракту ребалансировки, заимствование было успешно произведено. Заемный WAVAX был переведен на контракт ребалансировки.

  • Шаг 3: Злоумышленник еще раз вызвал b2a13230(), на этот раз используя ребалансер для заимствования WAVAX от своего собственного имени. Затем контракт использовал ранее заимствованный WAVAX (полученный из позиции жертвы) для пополнения позиции злоумышленника и погашения кредита, что позволило ему извлечь прибыль.

Заключение

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


REVLoans (Juicebox)

20 апреля 2026 года REVLoans, расширение для заимствований в Juicebox, подверглось атаке в сети Ethereum на сумму около $50,7 тыс. Функция borrowFrom() принимала предоставленный вызывающим источником источник учета без проверки его регистрации в протоколе; поддельный контекст с 36 знаками после запятой запустил «короткий путь» той же валюты, который ошибочно пересчитал балансы в 10^18 раз. Две транзакции — одна для посева инфлированной учетной записи и вторая для заимствования у легитимного пула по завышенной цене акций — позволили вывести 21,77 ETH.

Предыстория

Juicebox — это гибридный протокол сбора средств и кредитования в Ethereum. Каждый проект имеет собственный ERC20 токен доли (здесь именуемый REV) и казначейство, разделенное между одним или несколькими терминалами. Терминал — это контракт, который физически хранит часть активов проекта и выступает точкой входа/выхода. У одного проекта может быть несколько терминалов, зарегистрированных в JBDirectory, и каждый тройной набор (terminal, project, token) имеет JBAccountingContext, определяющий (десятичные знаки, валюта), используемые для учета этого токена внутри терминала. Таким образом, REV — это требование на объединение излишков всех терминалов проекта, а не на конкретный терминал.

Пользователь может внести актив в терминал в обмен на свежеотчеканенный REV, либо обменять REV в терминале на пропорциональную долю излишка (за вычетом настраиваемого налога, оставляющего часть средств для оставшихся держателей). REVLoans (0x2db6...1846), отдельный контракт поверх системы, добавляет возможность заимствования. Пользователь сжигает REV в качестве обеспечения и берет кредит у одного из терминалов проекта, который позже можно погасить в обмен на повторный минтинг обеспечения. Сумма кредита оценивается по той же математике, что и погашение, поэтому заимствование экономически эквивалентно выводу средств под то же обеспечение.

Цена акций REV рассчитывается как (totalSurplus + totalBorrowed) / (REV.totalSupply + totalCollateral). Включение totalBorrowed в числитель делает цену нейтральной при заимствовании/погашении; также это означает, что завышенный показатель totalBorrowed напрямую повышает цену акции и позволяет вывести непропорционально большую сумму под небольшое обеспечение.

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

Первопричина — непроверенный ввод параметра source. borrowFrom() принимает предоставленный пользователем REVLoanSource source (структура с полями .terminal и .token) без проверки того, зарегистрирована ли эта пара для данного revnetId. Оба поля напрямую поступают в формулы расчета, поэтому контекст учета, возвращаемый source.terminal, полностью контролируется злоумышленником. Когда поле currency в этом контексте совпадает с полем терминала назначения, протокол выбирает «короткий путь» той же валюты, пропускает оракул цен и принимает предоставленные десятичные знаки и значения баланса как авторитарные.

Непроверенный source затем записывается в _loanSourcesOf[revnetId] и totalBorrowedFrom[revnetId][source.terminal][source.token] с помощью _addTo(), который также не проводит проверку регистрации.

Как только пара (source, revnetId) добавляется в учет, функция _borrowableAmountFrom() преобразует запрос на кредит в сумму к выплате. Она строит surplus = totalSurplus + totalBorrowed из _totalBorrowedFrom(), а затем передает этот излишек в JBCashOuts.cashOutFrom() вместе с количеством обеспечения вызывающего лица и предложением акций.

Ошибка с десятичными знаками живет на уровень глубже, в _totalBorrowedFrom(). Она проходит через _loanSourcesOf и суммирует каждую запись через mulDiv(tokensLoaned, 10**decimals, pricePerUnit). На пути «той же валюты» pricePerUnit = 10**decimals (точность 18 знаков цели), поэтому формула сокращается до tokensLoaned без изменений. Баланс, сохраненный в 36-значном учете, попадает в 18-значную сумму ETH завышенным в 10^18 раз.

Усиление эффекта происходит в cashOutFrom(). base = mulDiv(surplus, cashOutCount, totalSupply): так как в surplus доминирует раздутый totalBorrowed, даже крошечное cashOutCount (обеспечение) приводит к непропорционально огромной выплате.

Анализ атаки

Атака состояла из двух транзакций. Первая «загрязняет» учет REVLoans: 0xc46cb7...dead1f. Вторая выводит средства из пула против легитимного терминала: 0x9adbd6...a8f938.

  • Шаг 1: Злоумышленник вызвал borrowFrom() с terminal и token в источнике кредита, указывающими на фейковый контракт, внеся небольшое количество REV в качестве обеспечения. REVLoans не проверял, зарегистрирован ли терминал для данного revnet, и распознается ли токен.
  • Шаг 2: REVLoans запросил у фейкового терминала контекст учета, который вернул поддельный (decimals=36, currency=ETH-code(61166)). Поскольку исходная и целевая валюты совпали, REVLoans применил «короткий путь», пропустил оракул и провел математику вывода над реальными излишками ETH легитимных терминалов, пересчитанными в 36-значную единицу злоумышленника, завысив фигуру в 10^18 раз.
  • Шаг 3: REVLoans зарегистрировал (fake terminal, fake token) в _loanSourcesOf и записал раздутое число в totalBorrowedFrom. Фейковый терминал «выплатил» средства, просто подтвердив получение их на хранение; реальные ETH не перемещались. Транзакция завершилась с манипулированным в сторону увеличения totalBorrowed и сожженным маленьким обеспечением REV.
  • Шаг 4: Злоумышленник снова вызвал borrowFrom(), на этот раз передав легитимный терминал ETH в качестве источника и крошечное обеспечение в REV. Математика вывода сработала уже в реальных 18-значных единицах ETH.
  • Шаг 5: При расчете totalBorrowed REVLoans прошел по _loanSourcesOf и встретил запись с шага 3. Поскольку валюта этой записи всё еще совпадала с ETH, снова сработал «короткий путь», и 36-значный сохраненный баланс был добавлен в 18-значную сумму ETH завышенным в 10^18 раз. totalBorrowed теперь доминировал фейковым долгом, а числитель цены акции был колоссально раздут.
  • Шаг 6: Математика вывода вернула сумму кредита, соразмерную раздутому числителю, который злоумышленник заранее настроил так, чтобы он был чуть меньше реального излишка легитимного терминала. Легитимный терминал выплатил эту сумму, практически осушив весь пул в пользу атакующего.

Заключение

Первопричина — два накладывающихся друг на друга пробела: пары (terminal, token) принимаются без проверки регистрации revnet, а «короткий путь» суммирует балансы источников в целевую сумму без нормализации различий в количестве десятичных знаков. Любой из этих факторов по отдельности был бы менее опасен; вместе они позволяют злоумышленнику внедрить произвольную запись totalBorrowedFrom и обналичить её по номинальной стоимости. Исправление: проверять (terminal, token) в соответствии с зарегистрированными терминалами revnet и нормализовать балансы по сохраненной шкале десятичных знаков источника перед их сложением.


Volo Vault

22 апреля 2026 года Volo, хранилище доходности в сети Sui, зарабатывающее за счет размещения пользовательских депозитов в протоколе кредитования Navi, потеряло около $3,5 млн после того, как был скомпрометирован приватный ключ оператора. В коде хранилища не было ошибок; злоумышленник просто выполнил легитимный операторский путь с украденными учетными данными и вывел депозиты Volo из Navi.

Предыстория

Volo — это хранилище (0xcd86...27fefa), Navi — подлежащий протокол кредитования. Хранилище владеет Navi AccountCap (capability-объект в Sui, разрешающий вывод средств с аккаунта Navi, принадлежащего Volo), и делегирует перемещения стратегии роли оператора. Чтобы внести или вывести средства в Navi, оператор вызывает start_op_with_bag_v2(), перенося AccountCap из хранилища во временный мешок (bag), а затем deposit_with_account_cap() / withdraw_with_account_cap_v2() использует этот Cap для перемещения средств.

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

Первопричина — операционная ошибка (утечка ключа), а не уязвимость уровня контракта. Путь стратегии Volo делегирует право на вывод средств любому, кто обладает приватным ключом оператора: функция start_op_with_bag_v2() выполняет всего две проверки (assert_operator_not_freezed и assert_single_vault_operator_paired), обе из которых лишь подтверждают, что предоставленная возможность принадлежит зарегистрированному оператору. После этого withdraw_with_account_cap_v2() принимает любого вызывающего, способного предъявить извлеченный AccountCap. Любой, владеющий приватным ключом оператора, может выполнить тот же путь, что и легитимные операции.

Анализ атаки

Анализ основан на транзакции AQw9wM...3RUS.

  • Шаг 1: Злоумышленник вызвал start_op_with_bag_v2 в @volosui/volo-vault::operation с украденным ключом оператора, поместив Navi AccountCap во временный мешок.
  • Шаг 2: Злоумышленник использовал bag::remove, чтобы извлечь AccountCap из мешка.

  • Шаг 3: Злоумышленник вызвал withdraw_with_account_cap_v2 в @navi-protocol/lending::incentive_v3 с извлеченным AccountCap, выведя депозиты Volo из Navi.

  • Шаг 4: Злоумышленник использовал bag::add, вернув AccountCap на место, закрыл операцию и вывел средства.

Заключение

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


Kipseli Router

22 апреля 2026 года роутер Kipseli в сети Base был взломан на сумму около $72,35 тыс. Роутер использует котировку, возвращаемую внешним инструментом (USDC-only), как сумму трансфера выходного токена, не проверяя, что выходной токен равен токену котировки. Злоумышленник обменял 0,04 WETH на cbBTC по пути, который котировщик на самом деле не поддерживает, получив возвращаемое значение в шкале USDC (92 610 395) как необработанные единицы cbBTC (≈0,926 cbBTC).

Предыстория

Kipseli Router (0x579f...9a07) — это контракт исполнения обмена, поддерживаемый внешней системой котирования. Контракт не является открытым; анализ основан на декомпилированном байт-коде. Вместо прямого расчета цен обмена из AMM-пулов в сети, он запрашивает у котировщика сумму вывода (amountOut) и выполняет трансфер на основе этого значения. В нормальном режиме пользователь отправляет tokenIn в кошелек протокола, а роутер вытягивает tokenOut из того же кошелька и пересылает получателю. Протокол настроен на один QUOTE_TOKEN, и логика котирования деноминирована в USDC (6 знаков).

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

Дефект охватывает два слоя. На стороне роутера функция 0xcce096f3() получает котировку v0 через котировщик 0x592() и передает её без изменений в 0xd88() как tokenOut.transferFrom(_wallet, receiver, v0). Роутер никогда не проверяет, что tokenOut равен QUOTE_TOKEN, поэтому USDC-значение (6 знаков) переводится как cbBTC (8 знаков). На стороне котировщика AMM-система PropAMM разработана исключительно для пар токен-USDC, но принимает неподдерживаемые маршруты (WETH -> cbBTC) без возврата (revert), молча игнорируя tokenIn и возвращая значение в шкале USDC, как если бы обмен был валидным.

Анализ атаки

Анализ основан на транзакции 0x96edee...3db3bb.

  • Шаг 1: Злоумышленник вызвал роутер с tokenIn=WETH и tokenOut=cbBTC. АмМ не поддерживал этот путь, но не сделал откат, а котировщик 0x592() вернул значение в шкале USDC — 92 610 395 (≈92,61 USDC).
  • Шаг 2: Роутер использовал это значение напрямую как сумму трансфера cbBTC. 0,04 WETH (≈$95) поступило в систему, а 92 610 395 единиц cbBTC (≈0,926 cbBTC, ≈$72,35 тыс.) ушло из кошелька протокола злоумышленнику.

Заключение

Эксплойт сработал, потому что ни одна из сторон не проверяла корректность входных данных. Исправления: на стороне роутера — подтверждать tokenOut == QUOTE_TOKEN или нормализовать котировку; на стороне котировщика — возвращать ошибку при неподдерживаемых путях, а не молча отдавать значение.


Purrlend

25 апреля 2026 года протокол кредитования Purrlend на HyperLiquid и MegaETH потерял حوالي $1,5 млн после компрометации приватного ключа. Злоумышленник захватил роль моста (bridge) и выпустил не обеспеченные pTokens (токены-квитанции Purrlend), после чего использовал их как обеспечение для вывода реальных активов из пула.

Предыстория

Purrlend (0x81d5...a702) — это протокол кредитования с моделью учета, аналогичной Aave. Когда пользователи вносят активы, они получают pTokens (аналог aTokens), представляющие их позицию и служащие обеспечением. В протоколе есть роли: администратор пула, администратор рисков и мост. Мост предназначен для кросс-чейн учета: он минтит pTokens для отражения депозитов в другой сети.

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

Триггером стала компрометация ключей администраторов и роли моста. Ошибка дизайна заключалась в отсутствии привязки минтинга pToken к каким-либо доказательствам кросс-чейн эскроу. Функция минтинга позволяет выпускать токены на любой адрес без проверки того, был ли реальный депозит в другой сети. В других местах протокола pTokens считаются валидным обеспечением. Таким образом, несанкционированный выпуск переводится напрямую в возможность вывода активов.

Анализ атаки

Анализ основан на транзакции 0xb96cff...dbbf24 в MegaETH.

  • Шаг 1: Злоумышленник через MultiSendCallOnly назначил себя администратором пула, рисков и моста, включив WETH как заемный актив.
  • Шаг 2: Выступая как мост, злоумышленник выпустил огромное количество pTokens на свой адрес без реального обеспечения.

  • Шаг 3: Злоумышленник использовал фейковые pTokens как обеспечение и вывел WETH из пула.

Заключение

Необходимо внедрить пороговые схемы управления (мультисиг) для чувствительных ролей и требовать Verifiable Proof of Escrow для минтинга через мост. Проверка доказательства при выпуске — это единственный надежный способ исключить риск компрометации одного ключа.


SingularityFinance

26 апреля 2026 года хранилище dynBaseUSDCv3 в сети Base потеряло ~$413 тыс. Из-за неверной настройки типа комиссии Uniswap V3 (42, которой не существует) оракул цен Resolve не существовал, и функция возвращала 0 вместо возврата ошибки (revert). Хранилище оценило резервы в 0, что позволило злоумышленнику выпустить почти все токены долей через крошечный депозит USDC и обналичить резервы.

Предыстория

Хранилище (0x67b9...4dcd) агрегирует доходные токены. Цена рассчитывается как (base, quote, fee) через TWAP из Uniswap V3.

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

При вызове IUniswapV3Factory.getPool с некорректным fee возвращается address(0). В коде реализован ошибочный возврат нуля вместо отката транзакции. В итоге totalAssets() учитывал только USDC, занижая общую стоимость активов.

Анализ атаки

Анализ на основе транзакции 0x00b949...8d3732.

  • Шаг 1: Флэш-заем на 100K USDC.
  • Шаг 2: Депозит. Из-за того, что остальные токены оценивались в 0, злоумышленник получил огромную долю акций.
  • Шаг 3: Погашение долей ( redemption) с получением всех имеющихся в хранилище активов.

Заключение

Необходимо проверять параметры оракула при деплое и валидировать результат getPool.


Scallop

26 апреля 2026 года Scallop на Sui потерял ~$142,7 тыс. Функция обновления наград не проверяла соответствие объекта отслеживания наград кошельку пользователя, что позволяло вытягивать фиктивный баланс из заброшенных объектов вознаграждения.

Предыстория

Scallop использует модель spool: пользователи вносят активы, получают MarketCoin, стейкают их и зарабатывают points, которые меняют на RewardsPool.

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

Дефект в spool::user::update_points кроется в отсутствии проверки account.spool_id == object::id(spool). Злоумышленник мог подставить старый, «заброшенный» объект Spool с накопленным огромным index, что позволяло мгновенно начислить себе огромное количество очков.

Анализ атаки

Анализ на основе транзакции 6WNDjC...NfVL.

  • Шаг 1: Создание аккаунта в целевом пуле.
  • Шаг 2: Вызов update_points с использованием заброшенного donor_spool с высоким коэффициентом index.
  • Шаг 3: Обмен начисленных (фейковых) очков на реальные токены в RewardsPool.

Заключение

Необходимо добавить проверку привязки spool в функции update_points и ограничить размер начисления очков за один вызов.

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

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

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

О компании BlockSec

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

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

Best Security Auditor for Web3

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

BlockSec Audit