DeFi. Безопасность. Взлом Aperture Finance & SwapNet
Перевод
Это вольный перевод отчёта. См. оригинал: https://blocksec.com/blog/17m-closed-source-smart-contract-exploit-arbitrary-call-swapnet-aperture
Введение
25 января 2026 года мы обнаружили серию подозрительных транзакций, нацеленных на контракты-жертвы, развёрнутые SwapNet и Aperture Finance в сетях Ethereum, Arbitrum, Base и BSC, с совокупными потерями, превышающими $17 млн.
(Если оценивать высокоуровнево) первопричина обоих инцидентов проста и уже была обозначена в наших первоначальных алертах [1, 2]: контракты-жертвы предоставляют возможность произвольного вызова из-за недостаточной валидации входных данных, что позволяет злоумышленникам злоупотреблять существующими токенными разрешениями (approvals) и вызывать transferFrom для вывода активов.
Однако оба набора контрактов-жертв являются закрытыми, а после декомпиляции разворачиваются в тысячи строк кода с глубоко вложенной и сложной ветвящейся логикой, что существенно усложняет анализ.
Более того, последующие пост-мортемы, опубликованные пострадавшими проектами [3, 4], в основном сосредоточены на мерах по устранению последствий и восстановлению, уделяя ограниченное внимание базовым техническим деталям.
В результате остаётся ряд неотвеченных вопросов, включая то, как были сконструированы уязвимые пути вызовов и почему существующие проверки не смогли предотвратить эксплуатацию.
В данном отчёте мы приводим более детальный технический анализ на основе декомпилированного байткода и ончейн-трасс исполнения. Хотя отсутствие исходного кода ограничивает видимость, анализ на уровне байткода достаточен для реконструкции уязвимой логики и выявляет несколько интересных наблюдений о дизайне контрактов, которые не очевидны из высокоуровневых алертов.
Мы начинаем с глубокого разбора инцидента SwapNet, после чего приводим детальный анализ инцидента Aperture Finance.
Инцидент SwapNet
Контекст
SwapNet [5] - это DEX-агрегатор, предназначенный для поиска оптимальных маршрутов свопов путём агрегирования ликвидности из нескольких ончейн-источников, включая AMM и приватных маркет-мейкеров. Протокол также позволяет пользователям указывать кастомные роутеры или пулы при выполнении свопов, обеспечивая дополнительную гибкость.
Данный инцидент обусловлен недостаточной валидацией пользовательских входных данных, что позволяет атакующему инициировать вызовы transferFrom() с произвольными параметрами.
В результате активы, которые ранее были одобрены (approved) для контрактов-жертв (например, 0x616000e384Ef1C2B52f5f3A88D57a3B64F23757e), могли быть переведены злоумышленнику.
Согласно декомпилированному байткоду, функция 0x87395540() не содержит корректной проверки критически важных входных параметров. Подменяя ожидаемый адрес роутера или пула адресом токена (например, USDC), контракт ошибочно рассматривает токен как допустимую цель исполнения. Это приводит к выполнению низкоуровневого вызова с calldata, контролируемым атакующим.
В результате контракт-жертва выполняет вызовы видаapprovedAsset.transferFrom(victim, attacker, amount),
что позволяет атакующему вывести (siphon) все активы, на которые ранее было выдано разрешение (approval).
Ход атаки
Против SwapNet было зафиксировано несколько атак. В качестве примера здесь используется транзакция в сети Base:0xc15df1d131e98d24aa0f107a67e33e66cf2ea27903338cc437a3665b6404dd57.
Атакующий просто вызвал функцию 0x87395540() контракта-жертвы, передав вредоносные входные данные. Этот вызов состоит из двух основных этапов.
1. Ключевая внутренняя переменная (например, v51) была установлена в значение USDC, что позволило обойти предусмотренную логику маршрутизации.
Был выполнен низкоуровневый вызов с calldata, контролируемой атакующим, в результате чего был вызван USDC.transferFrom(), и все одобренные (approved) USDC были выведены.
Сводка потерь, транзакции и затронутые контракты
Инцидент с SwapNet привёл к ориентировочным потерям в размере около $13,41M в нескольких сетях. В таблицах ниже приведено резюме ключевых транзакций эксплойта и адресов задействованных контрактов-жертв.
Инцидент Aperture Finance
Контекст
Aperture Finance [6] - DeFi-протокол, который управляет позициями концентрированной ликвидности, такими как LP-позиции Uniswap V3, от имени пользователей. Его закрытые смарт-контракты (например, 0xD83d960deBEC397fB149b51F8F37DD3B5CFA8913) позволяют пользователям минтить и управлять позициями Uniswap V3, используя нативные токены.
Предполагаемый рабочий процесс минтинга позиций Uniswap V3
При создании (mint) позиций Uniswap V3 через функцию 0x67b34120() контракт следует ожидаемому трёхэтапному процессу:
- Обёртывание нативных токенов
- Своп нативных токенов через внутреннюю функцию
0x1d33() - Минт позиций Uniswap V3
Проблема возникает на шаге 2: функция 0x1d33() выполняет кастомизированный своп через низкоуровневый вызов, при котором критически важные параметры (например, цель вызова и calldata), по всей видимости, контролируются пользователем и проверяются недостаточно строго, что позволяет выполнять непреднамеренные внешние вызовы. Более подробная информация приводится в следующих разделах.
Анализ первопричины
Аналогично случаю со SwapNet, инцидент Aperture Finance обусловлен недостаточной валидацией входных данных при низкоуровневых вызовах. При вызове 0x67b34120() внутренняя функция 0x1d33() выполняет низкоуровневый вызов, используя calldata, предоставленную пользователем, не накладывая жёстких ограничений на цель вызова или селектор функции.
Как показано на рисунке ниже, calldata, используемая для инициирования низкоуровневого вызова, полностью формируется на основе входных данных, переданных атакующим.
Это позволяет атакующим сконструировать вредоносную calldata, в результате чего выполняется approvedToken.transferFrom(victim, attacker, amount)
в контексте контракта-жертвы. В итоге могут быть выведены не только ERC-20 токены, но и одобренные NFT-позиции Uniswap V3.
Ход атаки
Против Aperture Finance было зафиксировано несколько атак. В этом разделе в качестве репрезентативного примера используется транзакция в сети Ethereum:0x8f28a7f604f1b3890c2275eec54cd7deb40935183a856074c0a06e4b5f72f25a.
Атакующий создал атакующий контракт:0x5c92884dFE0795db5ee095E68414d6aaBf398130.
Этот контракт вызвал функцию 0x67b34120() с вредоносными входными данными и отправил 100 wei ETH (то есть msg.value == 100).
a) Нативный ETH был обёрнут в WETH посредством вызова функции WETH.deposit().
b) Была вызвана внутренняя функция 0x1d33() для выполнения низкоуровневого вызова. На этом этапе в контексте контракта-жертвы выполняется вызовWBTC.transferFrom(victim, attacker, amount), что позволяет атакующему вывести (siphon) одобренные токены.
Стоит отметить, что в конце функции 0x1d33() была успешно пройдена проверка баланса. В частности, функция 0x1d33() сравнивала изменение баланса с ожидаемым выходом свопа (то есть значением varg2.word2), которое также было задано атакующим. В результате вызовы считались успешно выполненными, даже несмотря на то, что контракт фактически ничего не получил.
В завершение была вызвана функция NonfungiblePositionManager.mint() для минта позиции в пользу атакующего с использованием 100 wei WETH.
Интересные наблюдения
Сравнивая нормальные и аномальные транзакции минта, мы заметили, что в обоих случаях токены были одобрены (approved) одному и тому же спендеру (например, OKX DEX: TokenApprove), но при этом указывались разные адреса роутеров (то есть DexRouter и WBTC).
Это указывает на то, что контракт, вероятно, осуществляет проверку спендера в approve, но не валидирует фактическую цель исполнения вызова, оставляя критическую брешь, которую можно эксплуатировать через произвольные (arbitrary) вызовы.
Обычная транзакция:
https://app.blocksec.com/phalcon/explorer/tx/eth/0xc823b703c716fa9078e1d71714b734557bd540ddd1e41590dd73da7c5aba0200
Аномальная транзакция: https://app.blocksec.com/phalcon/explorer/tx/eth/0x8f28a7f604f1b3890c2275eec54cd7deb40935183a856074c0a06e4b5f72f25a
Сводка потерь, транзакции и затронутые контракты
Инцидент с Aperture Finance привёл к ориентировочным совокупным потерям в размере около $3,67 млн в нескольких сетях. В таблицах ниже приведено краткое описание ключевых транзакций эксплойта и соответствующих адресов контрактов-жертв.
Заключение
Хотя инциденты SwapNet и Aperture Finance затронули разные протоколы и сети, базовая проблема в обоих случаях не является сложной:
низкоуровневые вызовы, контролируемые пользователем, в сочетании с недостаточной валидацией входных данных в контрактах, на которые выданы токенные разрешения (approvals)
Эти инциденты служат напоминанием о том, что гибкость в дизайне контрактов должна быть тщательно сбалансирована со строгими ограничениями на вызовы, особенно в закрытых системах, где возможности внешнего аудита и рецензирования ограничены.