Утром 30 апреля 2026 года (UTC) атакующий вывел средства из хранилищ бессрочных контрактов Wasabi Protocol в Ethereum и Base, а позже в Berachain и Blast. Первые сообщения называли потерю около $4,5 млн, позже — $5,9 млн по четырём сетям, из них около $2 млн в Ethereum и $2,5 млн в Base12.
Ошибки в математике хранилищ не было. Единственным админом был один ключ — ключ деплойера. Кто им владел, тот мог выдавать роли и обновлять контракты. Этот пост отвечает на два вопроса: когда сеть впервые показала, что что-то не так, и сколько ещё лежало в хранилищах в тот момент.
Как был устроен доступ в Wasabi
Контракты бессрочных сделок Wasabi — прокси UUPS под управлением контракта PerpManager с AccessControl от OpenZeppelin. Роль ADMIN_ROLE в PerpManager может обновлять хранилища и пулы12.
- PerpManager в Ethereum:
0xc0b0…7e7d - PerpManager в Base:
0xec3e…d70e - Деплойер, державший роль админа:
0x5c62…c0b8
Один внешний адрес с полными правами админа и без таймлока — это значит, что хватит одного утёкшего ключа. Так и сказал Hypernative: «ключ деплойера — единственный админ»2.
Атака по шагам
Время — UTC. Время блока каждой транзакции ниже мы прочитали сами через публичный RPC3. Подписи к шагам взяты у Rekt1.
07:47:59 — Ethereum: ADMIN_ROLE выдана новому контракту
Деплойер вызвал grantRole на PerpManager в Ethereum и выдал ADMIN_ROLE адресу 0x878E…F3bF — контракту, развёрнутому для атаки (0x11ff…1ded).
Это первый сигнал в сети. Новый админ у контракта, который хранит деньги пользователей, выданный адресу без истории, никогда не бывает рутиной.
07:49:47 — Ethereum: 8 хранилищ одной транзакцией
Через 108 секунд после выдачи роли одна транзакция опустошила восемь хранилищ в Ethereum (0xcd77…0116). Rekt описывает обновление прокси хранилищ на код атакующего как часть этого шага1. Отдельно транзакции обновления мы не нашли, поэтому их время не приводим.
07:51:11 — Base: та же выдача роли
Тот же приём в Base: grantRole на PerpManager в Base (0x0b5d…52b7). Правило для Base на то же событие сработало бы через минуту-две. Алерт из Ethereum, уже летевший к команде, был более ранним предупреждением для Base.
07:52:37 — Base: хранилища опустошены
Через 86 секунд после выдачи роли в Base (0x10b3…7e40).
С 07:57:47 до 08:15:57 — второй заход
Атакующий вернулся. Вторая выдача роли в Base в 07:57:47 (0x1855…67a8), вторая выдача в Ethereum в 07:58:59 (0x985b…fac1), вывод из LongPool в Ethereum в 07:59:35 (0xbabb…c317), третья выдача в Base в 08:12:47 (0xa2ec…ce2f) и второй вывод в Base в 08:15:57 (0x6b18…9626).
От первой выдачи роли до последнего вывода прошло 28 минут. Алерт около 07:51 оставлял 8 минут до LongPool и 25 минут до второго вывода в Base.
Что не изменило бы ни одно правило
Как поставить такие правила в SmartAlert
- Добавьте контракт с управлением доступом — тот, где
AccessControlилиOwnable, — а не только хранилища. - Создайте правило «Смена роли», любое событие, Критично. Оно срабатывает на
RoleGrantedиRoleRevoked. - На каждом прокси создайте правило «Обновление прокси». Для прокси ERC-1967 SmartAlert предлагает его сам.
- На каждом хранилище добавьте правило перевод больше суммы в $ с
from= хранилище, для самого вывода. - Повторите на каждой сети, где вы развёрнуты. Атакующий шёл от сети к сети. Алерт первой сети — предупреждение для остальных.
- Отправляйте Критичные алерты тем, у кого есть ключ паузы. Telegram бесплатный.
См. как мониторить обновляемый прокси и чек-лист после аудита.
Источники
- 1.Rekt, Wasabi Protocol — Rekt: выдача роли админа, обновления UUPS, выводы, потеря по четырём сетям. ↩
- 2.Hypernative, When the deployer key is the only admin: разбивка потерь по сетям, устройство админ-доступа. ↩
- 3.Проверено SmartAlert в сети: время блоков всех транзакций в этом посте, через публичный RPC Ethereum и Base. ↩
- 4.SmartAlert, задержка алерта в Ethereum: 12 подтверждений (около 144 с), опрос головы сети до 12 с, запись до 15 с (P95), проверка правила раз в 60 с на Pro. ↩