Aztec Connect взламують на $2,19 млн через вразливість ZK-Rollup

iconMetaEra
Поділитися
AI summary iconКороткий зміст

Контекст

14 червня 2026 року використаний контракт Aztec Connect RollupProcessor (0xff1f2b4adb9df6fc8eafecdcbf96a2b351680455) був використаний. Зловмисник використав розрив між numRealTxs та decoded_slots, щоб за одну атомарну транзакцію вивести з L1-пулу приблизно 2,19 мільйона доларів США. Aztec Connect був виведений з експлуатації у березні 2024 року, але цей незмінний контракт продовжує залишатися під ризиком через зберігання залишкових активів користувачів. У цій статті повністю відновлено технічні деталі атаки на основі вихідного коду контракту та ланцюгових calldata.

Огляд атаки

Причина вразливості

Під час обробки розрахунків rollup, RollupProcessorV3 має структурний розрив між діапазоном ітерацій циклу розрахунку L1 та діапазоном, на який поширюється зобов’язання хешу публічного вводу ZK. Зловмисник використав цей розрив, щоб 31 із 32 слотів публічного вводу були зобов’язані ZK-доказом до кореня стану L2, не пройшовши жодної перевірки розрахунку на рівні L1-контракту.

Decoder.sol: numRealTxs повністю контролюється атакувачем

numRealTxs читається з зсуву 4516 у calldata і не має жодних он-чейн обмежень:

decoded_slots округлюється до найближчого кратного numTxsPerRollup, що є вимогою до розміщення даних у SHA256 precompile. Однак ця операція округлення створює проміжок між numRealTxs та decoded_slots, який може бути довільно заповнений атакуючим.

RollupProcessorV3.sol: цикл розрахунків охоплює лише numRealTxs слотів

Розрив безпеки

Безпечні припущення нормального процесу полягають у тому, що кожен відкритий вхідний слот або перевіряється на рівні L1-контракту (зменшення pendingDepositBalance при депозиті), або обмежується ZK-схемою через publicValue == 0. Але в цьому сценарії вразливості:

  1. SHA256 передкомпільовано для всіх 32 слотів (фактично перевірено вхід 8192 байти = 32 × 256 Б), вміст слотів gap був підтверджений ZK-доказом.
  2. Цикл розрахунку L1 охоплює лише перший слот, слоти [2..32] gap не підлягають жодній перевірці на рівні L1.
  3. Обмеження ZK-схеми на gap-слот publicValue (має бути 0) було обійдено або не застосовано атакувачем.

Три захисні лінії взаємозалежні, але жодна з них не може самостійно забезпечити безпеку для гап-слоту — коли відсутні обмеження ZK-кільця, рівень L1-контрактів також не може виявити цього.

Модель подвійного шляху розбіжності

Одні й ті ж дані calldata використовуються двома шляхами з різними верхніми межами:

Непорозуміння щодо того, які слоти враховуються (ZK вважає 32, L1 — лише 1), є коренем проблеми створення монет з нічого.

Процес атаки

Атака на транзакцію 0x074ec931…aee1 містить 14 викликів processRollup(), що мають двохсегментну структуру: «перші 7 мінтів + останні 7 виведень», усі виконані в одній атомарній транзакції.

Етап 1: Відлиття — отримання активів з нізвідки на L2 (Rollup #13277–13283, 7 разів)

1. Зловмисник EOA 0x0f18d8b44a740272f0be4d08338d2b165b7edd17 викликає контракт головного входу 0x06f585f74e0da633ae813a0f23fb9900b61d0fcd, активуючи селектор 0x6f3ce701.

2. Основний контракт послідовно викликає 3 проміжних контракти, кожен з яких жорстко закодовано містить кілька зловмисних calldata rollup. Ключові параметри кожного calldata:

  • numRealTxs = 1, rollupSize = 1024, numInnerRollups = 32
  • Перший слот (видимий у L1): proofId = 0 (noop), publicValue = 0
  • Слоти 2–32 (31 порожній слот, L1 невидимий): proofId = 1 (депозит), publicValue = N, publicOwner = адреса атакуючого L2
  • Зі відповідними ZK-доказами (кільця не обмежують gap-слот publicValue на 0)

3. Контракт-реле A послідовно викликає RollupProcessor.processRollup() (Rollup #13277–13281, 5 разів):

  • Перевірка ZK-доказу пройдена — SHA256-обов’язання охоплюють усі 32 слоти
  • Цикл підсумовування L1 end = 1 × TX_PUBLIC_INPUT_LENGTH = 1 слот, обробляє лише noop
  • Підімнені депозити у gap [2..32] були ZK-обіцяні до нового Merkle root → баланс атакуючого L2 отримав 5 × 31N

4. Проміжний контракт B обробляє Rollup #13282–13283 (2 рази) тим самим способом, в результаті чого баланс атакуючого на L2 знову отримує 2 × 31N. На цей момент атакуючий накопичив 7 × 31N безпідтриманих поповнень на L2, тоді як кошти на L1 не зменшилися навіть на копійку.

Етап 2: Виведення — обмін віртуального балансу L2 на активи L1 (Rollup #13284–13290, 7 разів)

Зловмисник обміняв усі баланси L2, отримані під час етапу відлиття, на активи L1 за допомогою 7 виведень у rollup:

  1. Rollup #13284 (DAI): withdraw() → RollupProcessor безпосередньо переказує 270 513,054 DAI → 0x0f18…edd17
  2. Rollup #13285 (wstETH): переказ 167.890 wstETH → атакувальник
  3. Rollup #13286 (yvDAI): переказ 4 873,857 yvDAI → атакувальник
  4. Rollup #13287 (yvWETH, передача управління контрактом C): переказ 16.570 yvWETH → атакуючий
  5. Rollup #13288 (LUSD): переказ 9 273,734 LUSD → атакувальник
  6. Rollup #13289 (yvLUSD): переказ 359,047 yvLUSD → атакувальник
  7. Rollup #13290 (ETH, остання транзакція): RollupProcessor переказав 908,987 ETH → атакуючому.

Одинарна атомна транзакція виконана успішно (gasUsed = 4 513 539), на рівні контракту неможливо часткове відкотити. Зловмисник отримав чистий прибуток приблизно $2,19 млн, повністю за рахунок легітимних активів користувачів RollupProcessor.

Відстеження коштів

На основі блокчейн-розслідування (на стані на 2026-06-15, приблизно через 1 день після інциденту), стан вкрадених коштів такий:

Всі активи були безпосередньо переведені з RollupProcessor через проміжний атакуючий контракт 0x06f585…d0fcD на атакуючий EOA 0x0F18D8b44a740272f0be4d08338d2b165b7EdD17 у одній транзакції. Проміжний контракт не мав залишків коштів.

Поточні вкрадені кошти на 100% залишаються незмінними на EOA нападника, відмивання коштів ще не розпочато.

Підсумок

Основний урок цієї атаки полягає в тому, що верхня межа циклу розрахунку ZK-Rollup-контракту повинна бути строго зіставлена з діапазоном обіцянки хешу ZK-відкритих вхідних даних. Коли існує розрив між межею циклу numRealTxs на рівні L1-контракту та decoded_slots SHA256-обіцянки, будь-яка безпекова припущення, що залежить від обмежень ZK-кіл для слотів у цьому розриві, може бути обійдена атакуючим — L1 повинна незалежно перевіряти кожен відкритий вхідний слот, обіцяний ZK-доказом, і не може делегувати цю відповідальність рівню кіл.

Команда безпеки SlowMist рекомендує проектним командам провести повний зовнішній аудит безпеки перед розгортанням системи Rollup, зосереджуючись на логічній узгодженості межі стану L1/L2, надійних межах декодування calldata та он-чейн вторинній верифікації публічних вхідних даних ZK. Для контрактів, які були зняти з експлуатації, але все ще тримають залишкові активи, рекомендується виконати порядкове перенесення або знищення активів, щоб усунути ризик постійного відкриття.

Цей матеріал підготовлений командою інтелектуальної інформації про загрози SlowMist на основі системи інтелектуальної інформації про загрози MistEye, платформи слідкування MistTrack та аналізу, запущеного SlowMist Agent AI. Будь ласка, зв’яжіться з нами з будь-якими питаннями або зворотним зв’язком.

Відмова від відповідальності: Інформація на цій сторінці може бути отримана від третіх осіб і не обов'язково відображає погляди або думки KuCoin. Цей контент надається лише для загального інформування, без будь-яких запевнень або гарантій, а також не може розглядатися як фінансова або інвестиційна порада. KuCoin не несе відповідальності за будь-які помилки або упущення, а також за будь-які результати, отримані в результаті використання цієї інформації. Інвестиції в цифрові активи можуть бути ризикованими. Будь ласка, ретельно оцініть ризики продукту та свою толерантність до ризику, виходячи з ваших власних фінансових обставин. Для отримання додаткової інформації, будь ласка, зверніться до наших Умов використання та Розкриття інформації про ризики.