Фон
В мае 2026 года проект ShapeShift FOX Colony столкнулся с атакой на контракт EtherRouterCreate3, развернутый на Arbitrum. Злоумышленник использовал механизм мета-транзакций контракта, применяя возможность «произвольного самозвонка», в сочетании с логикой автоматического авторизования DSAuth для address(this), чтобы обойти модификатор auth и заменить основной маршрутизирующий компонент контракта resolver на злонамеренную версию. В результате через delegatecall были очищены все активы ERC20, находившиеся в контракте. Суть атаки заключается в «конфликте семантики мета-транзакций и внутреннего самозвонка авторизации», что привело к полному обходу прав доступа.

Обзор атаки

Корень уязвимости
Произвольный самовызывающийся вызов executeMetaTransaction: address(this).call(callData) не фильтрует чувствительный селектор
Контракт EtherRouter сам по себе является обновляемой прокси-архитектурой, основанной на резолвере: для неизвестных селекторов функции, fallback() вызывает resolver.lookup(msg.sig) для поиска адреса реализации и выполняет через delegatecall. Функция мета-транзакции (executeMetaTransaction) маршрутизируется от старого резолвера 0x7490022b0e44aa65c030ac0d6728382a29458fc5 к контракту реализации 0x4e7f1e1e263678590007e89b7e129686ba7758d4. Этот контракт реализации не открыт, ниже представлены результаты декомпиляции:


Суть проблемы: намерение executeMetaTransaction — позволить пользователям выполнять некоторые несенситивные операции через подпись, но оно не фильтрует functionSignature. Атакующий может использовать свою действующую подпись, чтобы вызвать contract.setResolver(злонамеренный адрес).
Автоматическое разрешение DSAuth.isAuthorized: src == address(this) пропускает
EtherRouter.setResolver(address) защищён модификатором auth, который должен позволять вызывать только владельцу или авторитету:

Но в DSAuth.isAuthorized(address src, bytes4 sig) есть логика автоматического разрешения самовызывающихся вызовов:

Когда executeMetaTransaction вызывается через address(this).call(setResolver(...)), msg.sender в setResolver будет самим контрактом 0x5c59..., поэтому DSAuth автоматически пропускает запрос.
С точки зрения отдельных элементов, оба этих дизайна не являются очевидными уязвимостями — автоматическое разрешение самовызывающихся вызовов распространено во многих прокси-моделях, и сама механика мета-транзакции также разумна. Однако, когда обе логики существуют одновременно, возникает семантический конфликт: возможность «произвольного самовызывающегося вызова», предоставляемая мета-транзакцией, сталкивается с логикой «доверия к самовызывающимся вызовам» в DSAuth, что в совокупности создаёт полный обход прав.
Динамическое маршрутизирование delegatecall для EtherRouter.fallback(): полный контроль передан после захвата резолвера

После замены резолвера атакующему достаточно вызвать любой отсутствующий селектор функции на EtherRouter, fallback() безусловно делегирует вызов на злонамеренную реализацию под контролем атакующего.
Злонамеренный резолвер и реализация drain: реестр функций без привилегий + очистка активов address(this)
Атакующий предварительно развернул два контракта:
Злонамеренный резолвер
0x4e321af09012e15a67756522187c05b108b7ee0a (не открытый, декомпилированный):

Злонамеренная реализация Drain
0x0b971e0a8ecc7d5b2465c903cf75aeaedbfc39e2 (не открытый, декомпилированный):

Формула атаки для получения прибыли

Процесс атаки
Процесс атаки завершился всего за одну транзакцию, вся логика выполняется в конструкторе временного атакующего контракта 0x835a701fd76b96a76ee84de037d41f059ee29f5c.
Первый этап: развертывание злонамеренной инфраструктуры
EOA атакующего 0xeed236afb6967f74099a0a6bf078bc6b865fbf28 инициирует транзакцию, создавая временный атакующий контракт 0x835a701fd76b96a76ee84de037d41f059ee29f5c.
Атакующий контракт вызывает злонамеренный резолвер 0x4e321af09012e15a67756522187c05b108b7ee0a с set(bytes4,address), сопоставляя селектор drain(address,address) 0x837971e4 со злонамеренной реализацией drain 0x0b971e0a8ecc7d5b2465c903cf75aeaedbfc39e2.
Второй этап: захват резолвера через самовызывающуюся мета-транзакцию
Атакующий контракт вызывает executeMetaTransaction() жертвы 0x5c59d0ec51729e40c413903be6a4612f4e2452da. Поскольку эта функция отсутствует в ABI EtherRouter, вызов попадает в fallback(), где старый резолвер маршрутизирует к реализации мета-транзакции 0x4e7f1e....
executeMetaTransaction проверяет подпись через ecrecover, восстанавливая EOA атакующего, проверка подписи проходит (атакующий использует свою действующую подпись, а не поддельную).
executeMetaTransaction формирует самовызывающийся calldata setResolver(0x4e321af...) и выполняет address(this).call(callData). В этот момент контекст — жертва, поэтому msg.sender == 0x5c59.... DSAuth.isAuthorized() возвращает true, резолвер успешно заменён.
Третий этап: очистка активов через захваченный резолвер
Атакующий контракт вызывает drain(USDC, 0xeed236...) на жертве. Эта функция отсутствует в нативном ABI EtherRouter и попадает в fallback(). Захваченный резолвер возвращает злонамеренную реализацию drain 0x0b971e0..., жертва выполняет delegatecall. Злонамеренный код запрашивает USDC.balanceOf(0x5c59...) и получает 132704591501 (то есть 132,704.591501 USDC), затем вызывает USDC.transfer() для прямого перевода на EOA атакующего.
Атакующий контракт снова вызывает drain(0xf929..., 0x835a701f...), по тому же пути переводя украденный промежуточный токен в количестве 841086343608217839604694.
Атакующий контракт обменивает украденный промежуточный токен через Router 0x4752ba5dbc23f44d87826276bf6fd6b1c372ad24 на Pair 0x5f6ce0ca... за 1.949506469643782660 WETH, который непосредственно переводится на EOA атакующего.
Закрытие прибыли

Отслеживание средств
Анализируя атакующее EOA 0xeed236afb6967f74099a0a6bf078bc6b865fbf28 с помощью SlowMist MistTrack:
Основные следы:
Relay.link — $4,368.08, DEX агрегатор для обмена активов.
LI.FI — $137,073.66, межсетевой/DEX агрегатор.
Средства, украденные атакующим, поступили в Spark.fi Saving, и команда SlowMist будет продолжать отслеживать движение средств на соответствующих адресах.

Резюме
Основной урок этой атаки: когда контракт одновременно имеет две семантики — «произвольный самовызывающийся вызов мета-транзакции» и «автоматическое разрешение самовызывающихся вызовов» — они образуют полный обход прав. Это не единичная кодовая уязвимость, а неизбежный результат семантического конфликта между компонентами. Разработчики контрактов должны четко разграничивать границы чувствительных функций при проектировании мета-транзакций или механики ретрансляции, по крайней мере, поддерживать список запрещённых селекторов в executeMetaTransaction и осторожно использовать автоматическое разрешение для src == address(this). Команда безопасности SlowMist рекомендует проектам проводить полные внешние аудиты безопасности перед развертыванием.
Примечание: данный материал является техническим анализом безопасности и предназначен только для учебных целей, не является инвестиционной рекомендацией. Все данные на цепи получены из открытой информации.

