Поддельный Safe обманул доверенный модуль

Введение
Safe — это кошелёк с мультиподписью. Для совершения операций обычно требуется несколько подтверждений от владельцев. Но к Safe можно подключать модули — дополнительные программы, которые автоматизируют действия, например, управление займами или ребалансировку активов.
FlashLoopAdapter — это модуль, который использовался для управления заёмными позициями в протоколе Aave V3. Он позволял автоматически открывать и закрывать займы под залог.
Aave V3 — это протокол кредитования в DeFi. Пользователи вносят криптовалюту как залог и получают займы в других активах.
weETH — это «обёрнутый» стейкинговый эфириум. Когда пользователь вкладывает ETH в сервис для получения дохода, он получает weETH — токен, который растёт в стоимости.
Ход инцидента
-
Злоумышленник взял мгновенный кредит (flash-loan) на 11 537 WETH у протокола Morpho. Эти средства нужны были для начала атаки.
-
Около 1 335 WETH из этого кредита он направил на погашение долга первого Safe-кошелька в Aave. Это было необходимо, чтобы разблокировать залог, который находился под этим займом.
-
После погашения долга из Aave были выведены около 1 306 weETH — ранее заблокированный залог.
-
Со второго Safe-кошелька были получены ещё около 6,4 weETH.
-
Всего было собрано 1 312,9 weETH. Они были обменены через несколько децентрализованных бирж — Curve, Fluid, Uniswap V3 и Uniswap V4 — примерно на 1 449 WETH.
-
Из этой суммы 1 335 WETH вернули в Morpho для погашения мгновенного кредита. Разница составила около 114 WETH — это чистый ущерб для Safe-кошельков.
-
В конце транзакции 114 WETH были развёрнуты в обычный ETH.
Почему это стало возможным
Модуль FlashLoopAdapter некорректно проверял, кто имеет право инициировать операции. Его функции
open() и close() доверяли внешнему контракту, который сообщал, что необходимый модуль якобы разрешён. Злоумышленник создал собственный контракт, который прошёл эту проверку, выдав себя за легитимный Safe.Дополнительно функция
_swap() позволяла атакующему самостоятельно задавать маршрут обмена и данные вызова. В качестве маршрута он указал настоящий Safe жертвы, а через данные вызова передал команду execTransactionFromModule().Для Safe-кошелька такая команда выглядела легитимной, потому что приходила от уже разрешённого модуля FlashLoopAdapter. Поэтому кошелёк выполнял операции без подписи владельцев — он считал, что модуль действует в рамках своих полномочий.
Детали расследования: движение средств
Мгновенный кредит на 11 537 WETH поступил на адрес:
С этого адреса около 1 335 WETH были направлены на погашение долга первого Safe. После этого на этот же контракт были выведены ~1 306 weETH из Aave и ещё ~6,4 weETH со второго Safe.
Заключение
Инцидент с Safe FlashLoopAdapter показал, что доверие к внешним контрактам без должной проверки их подлинности может обойти всю систему мультиподписи. Модуль принял поддельный Safe за настоящий и выполнил команды от его имени, а настоящие Safe-кошельки выполнили операции, считая их исходящими от доверенного модуля.


