ИнцидентыSmartAlert7 мин чтения

Взлом Ronin Bridge, август 2024: за 48 минут до первого вывода порог голосов был равен 0, и ни одно событие об этом не сказало

Как произошёл взлом моста Ronin в августе 2024: плановое обновление обнулило порог голосов, атака по шагам и алерт, который сработал бы за 45 минут до вывода.

6 августа 2024 года команда Ronin обновила свой мост в Ethereum. Через 48 минут MEV-бот вывел из него около 4 000 ETH, а ещё через полчаса другой адрес забрал 2 млн USDC. Всего ушло около $12 млн. Бот принадлежал белому хакеру, и средства вернулись в тот же день123.

Большинство взломов мостов начинается с украденного ключа. Этот — нет. Никто ничего не взламывал, и ничего необычного контракт не выпустил. Обновление было собственным обновлением команды, и событие Upgraded ожидалось. Сломалось число внутри контракта, и увидеть его можно было только вопросом к контракту.

Как мост Ronin одобряет вывод

Ronin — блокчейн, на котором работает Axie Infinity. У его моста в Ethereum две важные здесь части23:

  • Шлюз 0x6419…AF08 в Ethereum хранит перенесённые ETH, USDC и другие токены. Это прокси: адрес остаётся, код за ним можно заменить.
  • Операторы моста следят за Ronin. Когда пользователь выводит средства в Ethereum, операторы подписывают вывод. У каждого оператора есть вес голоса.

Шлюз платит, только если суммарный вес подписавших дотягивает до порога. Его возвращает view-функция minimumVoteWeight(). До 6 августа она возвращала 1540 — 70% суммарного веса операторов34.

Ещё шлюз ограничивает, сколько может забрать один вывод: около 4 000 ETH или 2 млн USDC2. Запомните это. Он ограничивал размер каждого вывода.

Изменения шлюза проходят через MainchainBridgeManager 0xa714…fab0: предложению нужны подписи операторов, затем его может отправить в сеть кто угодно. Для аварий есть отдельный PauseEnforcer 0xe514…bcd4, который останавливает шлюз одним вызовом.

Что пошло не так при обновлении

Обновляемый контракт хранит состояние в прокси. Новому коду часто нужно новое состояние, поэтому к нему прилагается инициализатор, который его заполняет. У шлюза их было несколько, по номерам версий. Версия 3 задавала _totalOperatorWeight — суммарный вес, от которого считается порог. Версия 4 задавала новый вспомогательный контракт для WETH23.

Обновление 6 августа перескочило сразу к версии 4. Версию 3 прокси никогда не запускал. Поэтому _totalOperatorWeight остался равен 0, а 70% от 0 — это 03.

Это видно в логах обновления. В них есть Upgraded и Initialized(4), но нет Initialized(3)4.

Атака по шагам

Время — UTC, время блоков Ethereum. Каждую транзакцию ниже мы прочитали в сети сами4.

08:48:47 — плановое обновление

Адрес 0xe880…f059 отправил в MainchainBridgeManager предложение раунда 5 с 16 подписями операторов (0x855d…767e, блок 20468438). Предложение сделало шесть вызовов к шлюзу. Среди них: добавить WBTC, переключить код на 0xfc27…20Ee и запустить initializeV4, затем сменить админа прокси. Последние два вызова упали, первые четыре прошли4.

Мы прочитали minimumVoteWeight() через eth_call на архивных блоках4:

20468437
Когдаблок перед обновлением
minimumVoteWeight()1540
20468438
Когда08:48:47, обновление
minimumVoteWeight()0
20468678
Когдаблок перед первым выводом
minimumVoteWeight()0

С этого блока выводу из шлюза не нужны были подписи операторов вообще. Любой, кто заметил, мог забирать до лимита одного вывода сколько угодно раз.

09:37:23 — 3 996 ETH

Адрес 0x1a56…3cc1 вызвал свой MEV-контракт 0x4Ab1…3c0B, а тот вызвал submitWithdrawal у шлюза. Шлюз выплатил 3 996,09 ETH (0x2619…a6cb, блок 20468679)4. Это через 48 минут после обновления.

10:11:47 — 2 млн USDC

Адрес 0xFDe0…455A вывел 1 998 046,88 USDC на 0x6980…BDD0 (0xbce5…0ad8, блок 20468848)4.

10:15:23 — пауза

Адрес 0x8B35…8f57 вызвал triggerPause() у PauseEnforcer (0x90a1…c090, блок 20468866)4. Это через 38 минут после первого вывода и через 86 минут после обновления. Ronin сообщила, что поставила мост на паузу примерно через 40 минут после первого вывода1.

Потом — деньги вернулись

Белый хакер за MEV-ботом вернул ETH, а примерно через два часа и USDC. Ronin выплатила награду $500 тыс. По данным The Block, в мосту в тот момент было больше $850 млн16. Лимит на один вывод ограничивал каждую транзакцию примерно 4 000 ETH или 2 млн USDC2. С реагированием помог SEAL 9113.

Что не изменило бы ни одно правило

Как поставить это правило в SmartAlert

  1. Добавьте контракт и откройте его. Правила состояния контракта — на тарифе Pro и выше.
  2. Создайте правило «Состояние контракта». Выберите функцию, например minimumVoteWeight(), и нажмите «Проверить функцию», чтобы увидеть текущий ответ.
  3. Выберите условие «Меньше порога» с порогом ниже обычного значения, например 1. Для моста — вызывать каждую минуту.
  4. Создайте его до следующего обновления. Тогда первый вызов после обновления покажет, пережило ли его число.
  5. Добавьте и правила на события. Upgraded говорит, когда сменился код, правило состояния — что он изменил. Для нативного ETH добавьте шлюз в «Адреса» и создайте правило «Общий баланс адреса в $ ниже порога».
  6. Выберите, куда идут алерты. Telegram бесплатный.

Все рецепты алертов.

Переезжаете с OpenZeppelin Defender? Как каждый монитор Defender переносится в правило SmartAlert — в руководстве по переезду, а сравнение с Defender — отдельной страницей.

Источники

  1. 1.Ronin Network в X, заявление об инциденте: пауза, около 40 минут, белый хакер. ↩
  2. 2.Rekt, Ronin Network — Rekt II: пропущенный инициализатор, лимит на один вывод, суммы. ↩
  3. 3.Three Sigma, разбор атаки на Ronin Network: initializeV3 и _totalOperatorWeight, порог 70%, SEAL 911. ↩
  4. 4.Проверено SmartAlert в сети: minimumVoteWeight() через архивный eth_call на блоках 20468437, 20468438 и 20468678; транзакция обновления, её шесть вызовов, их результаты и логи (Upgraded, Initialized(4), нет Initialized(3)); два вывода и пауза — время блоков, отправители и суммы — через Blockscout API. ↩
  5. 5.SmartAlert, задержка алерта в Ethereum: 12 подтверждений (около 144 с), опрос головы сети до 12 с, запись до 15 с (P95), проверка правила раз в 60 с на Pro. ↩
  6. 6.The Block, материал о паузе моста Ronin: возврат средств, награда $500 тыс., больше $850 млн в мосту. ↩
  7. 7.CertiK, Bybit incident technical analysis. ↩
  8. 8.KiloEx, Security incident root cause analysis. ↩

Попробуйте бесплатно

Достаточно адреса. Первые 14 дней действуют лимиты Pro, а правила проверяются на истории до включения.

Ещё статьи