5 марта 2025 года контракт стороннего резолвера, интегрированный с протоколом Fusion V1 от 1inch, подвергся скоординированной атаке, результатом которой стали общие убытки на сумму более 5 миллионов долларов. Первопричиной стала небезопасная реконструкция calldata в процессе расчетов, где контролируемая злоумышленником длина взаимодействия (interaction length) вызвала антипереполнение указателя (pointer underflow) при сборке суффикса, что позволило внедрить поддельные расчетные данные. Эксплойт стал возможен из-за ошибочной границы доверия: контракты резолверов неявно доверяли всем calldata, пересылаемым расчетным контрактом, основываясь исключительно на msg.sender, что приводило к тому, что контролируемые злоумышленником данные наследовали авторизацию расчетного уровня, несмотря на прохождение всех проверок контроля доступа.
Этот инцидент выделяется как одна из самых изощренных DeFi-атак 2025 года не из-за нового финансового примитива, а из-за использования низкоуровневых предположений об ABI и макете памяти, что размывает границы между уязвимостями смарт-контрактов и классическими методами эксплуатации бинарного кода.
Справочная информация
Протокол Fusion V1 от 1inch
1inch — это агрегатор децентрализованных бирж (DEX), который объединяет ликвидность с нескольких DEX для предоставления пользователям оптимизированного исполнения свопов. Построенный на инфраструктуре лимитных ордеров агрегатора, 1inch Fusion внедряет модель сопоставления ордеров на основе голландского аукциона, позволяя пользователям указывать гибкие параметры исполнения, такие как ценовые диапазоны и временные окна для свопа.
В модели Fusion ордера пользователей не исполняются напрямую самим протоколом. Вместо этого они исполняются специализированными участниками, называемыми резолверами (resolvers). Резолверов можно рассматривать как привилегированных, внесенных в «белый список» участников: чтобы получить право на эту роль, они должны стейкать токены для получения достаточного количества Unicorn Power, что служит механизмом экономического контроля.
Операционно резолверы запускают собственную инфраструктуру вне сети (off-chain), которая взаимодействует с бэкенд-API 1inch для поиска ордеров пользователей и определения того, когда и как их исполнять. Как только резолвер решает исполнить ордер, он отправляет транзакцию в сети через выделенный аккаунт в расчетный контракт (Settlement), который и выполняет фактический своп. (Хотя Fusion включает в себя конкурентный голландский аукцион между резолверами, этот механизм не является существенным для понимания атаки и поэтому здесь опущен; для простоты мы предполагаем, что резолвер может напрямую удовлетворить условия ордера пользователя.)
При выполнении свопа резолвер может либо привлечь ликвидность с внешних рынков для обеспечения лучшего исполнения, чем минимальные требования пользователя, либо напрямую провести сделку, используя собственные активы. Любая избыточная стоимость, созданная в процессе, начисляется резолверу в качестве прибыли. Описанная в этом блоге атака нацелена именно на такой контракт резолвера. В частности, на контракт, используемый для маркет-мейкинга в сети, который хранил активы и имел предоставленные разрешения (approvals) для расчетного контракта, что в конечном итоге привело к потере этих активов.
Обработка и исполнение ордеров
Исполнение ордеров в 1inch Fusion координируется расчетным контрактом (Settlement) и следует модели рекурсивных расчетов, управляемых данными взаимодействия, а не линейным потоком исполнения.
Процесс начинается, когда резолвер вызывает функцию settleOrders(), передавая calldata, которые кодируют первый ордер вместе с полезной нагрузкой взаимодействия (interaction payload). Вместо того чтобы пытаться урегулировать все ордера сразу, Settlement обрабатывает их постепенно через повторные вызовы внутренней функции _settleOrder().
Для каждого ордера функция _settleOrder() реконструирует вызов протокола лимитных ордеров (AggregationRouterV5.fillOrderTo()) в памяти. Во время этой реконструкции Settlement добавляет дополнительные контекстные данные резолвера к данным взаимодействия в виде динамического суффикса. Этот суффикс содержит метаданные исполнения, такие как идентификатор резолвера и накопленные комиссии, и воспринимается протоколом лимитных ордеров как непрозрачные данные. Они пересылаются без изменений и позднее декодируются самим Settlement во время обратного вызова взаимодействия.
После заполнения ордера управление возвращается в Settlement через функцию fillOrderInteraction(). Основываясь на данных взаимодействия, Settlement либо продолжает расчеты путем рекурсивного вызова функции _settleOrder() с оставшейся полезной нагрузкой, либо переходит к этапу финализации. На последнем этапе Settlement передает управление контракту резолвера путем вызова его функции resolveOrders(), передавая собранный контекст исполнения.
Такая конструкция позволяет последовательно рассчитывать несколько ордеров в рамках одной транзакции, откладывая специфическую логику резолвера до завершения всех шагов исполнения на уровне протокола.
Анализ уязвимости
Уязвимость возникает из-за небезопасной логики реконструкции calldata во внутренней функции _settleOrder(), в частности, при размещении динамического суффикса, добавляемого к данным взаимодействия.
Контракты резолверов защищены явным контролем доступа: они требуют, чтобы вызовы исходили от расчетного контракта (Settlement) и чтобы адрес резолвера, предоставленный во время разрешения, совпадал с самим контрактом резолвера. Эти проверки предполагают, что идентификатор резолвера, распространяемый через процесс расчетов, сконструирован правильно и остается неизменным. Это предположение зависит от корректности того, как данные резолвера кодируются и добавляются во время реконструкции calldata.
Конкретно, функция resolveOrders() резолвера применяет две проверки:
require(msg.sender == _settlement, OnlySettlement());
require(this == resolver, NotTaker());
Первая проверка подтверждает, что вызов исходит от расчетного контракта. Вторая подтверждает, что параметр resolver (декодированный из суффикса) совпадает с собственным адресом контракта резолвера. При нормальной работе оба поля устанавливаются правильно функцией _settleOrder(). Во время атаки обе проверки по-прежнему проходят: злоумышленник работает внутри расчетного потока (а не вне его), поэтому msg.sender действительно является расчетным контрактом, а поле resolver декодируется из поддельного суффикса, куда злоумышленник записал адрес целевого (жертвенного) резолвера.
Во время расчетов _settleOrder() реконструирует calldata в памяти и добавляет динамический суффикс, который содержит контекст исполнения, специфичный для резолвера. Этот суффикс включает не только адрес резолвера, но и информацию об ордере, на которую резолвер полагается для интерпретации расчета как законного. Суффикс записывается по адресу в памяти, вычисляемому как ptr + interactionOffset + interactionLength, где interactionLength считывается непосредственно из calldata как полное 32-байтовое значение.
Поскольку это вычисление смещения выполняется без проверки границ (bounds checking), злоумышленник может контролировать interactionLength, чтобы вызвать арифметическое переполнение и манипулировать тем, куда записывается суффикс в памяти. Поступая так, злоумышленник может заменить предназначенный суффикс поддельным, предоставляя произвольный адрес резолвера вместе с контролируемым злоумышленником контекстом ордера.
В результате, когда расчетный поток позже декодирует суффикс, он может интерпретировать исполнение как исходящее от законного резолвера с использованием действительных данных ордера, даже если ни одно из этих предположений не является верным. Злоумышленник фактически способен внедрить свою собственную версию суффикса ордера, притворяясь резолвером, чтобы обменять несколько вей на миллионы.
Анализ атаки
Возьмем в качестве примера транзакцию 0x74bc. Атака начинается с создания пяти действительных ордеров, которые меняют ничтожное количество токенов (несколько вей) на непропорционально большую сумму вывода. Эти ордера синтаксически верны и проходят проверки на уровне протокола.
Затем злоумышленник дополняет calldata ордера большой областью нулевых байтов. Это дополнение служит контролируемой областью памяти, которая позже будет перезаписана из-за некорректного размещения суффикса.
Важно отметить, что злоумышленник указывает неверное значение interactionLength для последнего ордера. Значение выбрано как 0xffff…fe00, что соответствует -512 при интерпретации как целое число со знаком. Поскольку interactionLength рассматривается как 256-битное значение без знака и используется непосредственно в арифметике указателей, это вызывает арифметическое переполнение при вычислении смещения записи суффикса.
Приведенная ниже вредоносная структура взаимодействия рассматривается как суффикс:
От поддельного суффикса к извлечению активов
Когда Settlement обрабатывает последний ордер, переполнение приводит к тому, что законный суффикс записывается в область, заполненную нулевыми байтами, безвредно перезаписывая нули. Поддельный суффикс, размещенный злоумышленником в фактической позиции чтения, затем интерпретируется как законный расчетный контекст.
Поддельный суффикс устанавливает флаг финализации, заставляя Settlement перейти от рекурсивной обработки ордеров к этапу финализации. Settlement вызывает resolveOrders() на адрес, декодированный из суффикса, который является жертвенным резолвером. Как описано в разделе анализа уязвимости, обе проверки контроля доступа проходят: msg.sender является расчетным контрактом (вызов проходит через законный расчетный путь), а resolver совпадает с адресом жертвы (записанным в поддельный суффикс злоумышленником).
После этого жертвенный резолвер обрабатывает массив tokensAndAmounts, который включает адреса активов и суммы, выбранные злоумышленником. Поскольку резолвер ранее предоставил расчетному контракту разрешения на использование токенов для нормальной работы, перевод выполняется. Резолвер отправляет свои хранящиеся активы для исполнения ордеров, которые требовали всего несколько вей входных данных, что привело к убыткам на сумму более 5 миллионов долларов.
Резюме
Этот инцидент был нацелен на сторонний контракт резолвера, интегрированный с устаревающим протоколом Fusion V1 от 1inch, и привел к существенным убыткам, что подчеркивает несколько важных уроков:
- Небезопасные предположения о данных: Системы, полагающиеся на динамически создаваемые входные данные, не должны предполагать целостность длин, смещений или структуры, когда любая часть данных может быть подвержена влиянию пользователя.
- Цепочки неявного доверия: Проверки безопасности могут быть подорваны, когда контракты полагаются на вышестоящие компоненты в части сохранения критического контекста, вместо того чтобы проверять его на каждой границе.
- Площадь устаревших компонентов: Поддержка устаревших компонентов увеличивает поверхность атаки и делает протоколы более уязвимыми к новым методам эксплуатации.
- Шаблоны кросс-доменных атак: Уязвимости, типичные для традиционного программного обеспечения, могут вновь проявляться в сети (on-chain), когда задействована низкоуровневая логика памяти или макета данных.
Ссылки
-
https://blog.decurity.io/yul-calldata-corruption-1inch-postmortem-a7ea7a53bfd9
-
https://paragraph.com/@cookies-research/1inch-fusion-cost-efficient-mev-resistant-swaps
-
https://blog-zh.1inch.com/fusion-swap-resolving-onchain-component/#steps-5-6
О компании BlockSec
BlockSec — это полнофункциональный провайдер услуг в области безопасности блокчейна и крипто-комплаенса. Мы создаем продукты и услуги, которые помогают клиентам проводить аудит кода (включая смарт-контракты, блокчейн и кошельки), перехватывать атаки в режиме реального времени, анализировать инциденты, отслеживать незаконные средства и соблюдать обязательства AML/CFT на протяжении всего жизненного цикла протоколов и платформ.
BlockSec опубликовала множество статей по безопасности блокчейнов на престижных конференциях, сообщила о нескольких атаках «нулевого дня» (zero-day) на DeFi-приложения, заблокировала множество хакерских атак, сохранив более 20 миллионов долларов, и обеспечила безопасность криптовалют на миллиарды долларов.
-
Официальный сайт: https://blocksec.com/
-
Официальный Twitter-аккаунт: https://twitter.com/BlockSecTeam



