Aztec Connect bị khai thác 2,19 triệu USD do lỗ hổng ZKRollup

Ngày 14/06/2026, hợp đồng RollupProcessor của Aztec Connect đã ngừng hoạt động (0xff1f2b4adb9df6fc8eafecdcbf96a2b351680455) bị khai thác. Kẻ tấn công rút khoảng 2,19 triệu USD tài sản từ pool L1 chỉ trong một giao dịch nguyên tử, bằng cách tạo "khoảng trống biên" giữa numRealTxs và decoded_slots. Dù Aztec Connect đã bị khai tử từ tháng 03/2024, hợp đồng bất biến này vẫn trở thành điểm lộ rủi ro do còn nắm giữ tài sản tồn dư của người dùng. Bản phân tích dưới đây tái dựng chi tiết kỹ thuật từ mã nguồn hợp đồng và calldata on-chain. Tổng quan lỗ hổng Nguyên nhân gốc nằm ở độ lệch cấu trúc giữa phạm vi các chu kỳ quyết toán L1 mà RollupProcessorV3 duyệt qua và phạm vi được cam kết bởi hash đầu vào công khai (public input hash) của ZK. Kẻ tấn công lợi dụng độ lệch này để đưa nội dung của 31/32 public input slot vào state root L2 thông qua ZK proof mà không trải qua bước kiểm tra quyết toán tương ứng ở lớp hợp đồng L1. Điểm mấu chốt trong Decoder.sol: numRealTxs hoàn toàn do kẻ tấn công điều khiển numRealTxs được đọc từ calldata tại offset 4516 mà không có ràng buộc on-chain. decoded_slots được làm tròn lên bội số gần nhất của numTxsPerRollup để khớp bố cục dữ liệu của precompile SHA256. Việc làm tròn này tạo ra một vùng "gap" giữa numRealTxs và decoded_slots, nơi kẻ tấn công có thể tự do nhét dữ liệu. Trong RollupProcessorV3.sol: vòng quyết toán chỉ bao phủ numRealTxs slot Chu kỳ quyết toán L1 chỉ xử lý số slot tương ứng với numRealTxs. Đổ vỡ các giả định an toàn Giả định bình thường: mỗi public input slot либо được xác thực ở lớp hợp đồng L1 (ví dụ giảm pendingDepositBalance khi nạp) либо bị ràng buộc trong ZK circuit để publicValue == 0. Ở kịch bản này: - Precompile SHA256 bao phủ đủ 32 slot (đầu vào 8192 bytes = 32 × 256 bytes) và nội dung các gap slot đã được "cam kết" qua ZK proof. - Vòng quyết toán L1 chỉ xử lý slot đầu tiên; các gap slot [2..32] không bị kiểm tra ở cấp L1. - Ràng buộc trong ZK circuit đối với publicValue của gap slot (đáng ra phải bằng 0) bị kẻ tấn công vượt qua hoặc không được áp đặt. Ba lớp phòng vệ phụ thuộc lẫn nhau nhưng không lớp nào tự đảm bảo an toàn cho gap slot; khi ràng buộc ZK bị thiếu, lớp L1 cũng không phát hiện được. Mô hình "lệch hai đường xử lý" Cùng một calldata đi qua hai luồng với giới hạn trên khác nhau: ZK hiểu có 32 slot, L1 chỉ tính 1 slot. Sự khác biệt "slot nào được tính" này là nguồn cơn tạo tài sản từ không khí. Diễn biến tấn công Giao dịch 0x074ec931…aee1 gồm 14 lần gọi processRollup(), theo mô thức hai pha "7 lần mint rồi 7 lần rút" trong một giao dịch nguyên tử. Pha 1: Mint — tạo tài sản trên L2 (Rollup #13277–#13283, 7 lần) 1) EOA của kẻ tấn công 0x0f18d8b44a740272f0be4d08338d2b165b7edd17 gọi hợp đồng điều phối 0x06f585f74e0da633ae813a0f23fb9900b61d0fcd, kích hoạt selector 0x6f3ce701. 2) Hợp đồng điều phối lần lượt gọi ba hợp đồng relay, mỗi relay chứa sẵn nhiều calldata rollup độc hại. Tham số chính của mỗi calldata: - numRealTxs = 1, rollupSize = 1024, numInnerRollups = 32 - Slot 1 (L1 nhìn thấy): proofId = 0 (noop), publicValue = 0 - Slot 2–32 (31 gap slot, L1 không thấy): proofId = 1 (deposit), publicValue = N, publicOwner = địa chỉ L2 của kẻ tấn công Kèm theo ZK proof tương ứng (circuit không ràng buộc publicValue của gap slot về 0). 3) Relay A gọi liên tiếp RollupProcessor.processRollup() (Rollup #13277–#13281, 5 lần): - Verifier xác nhận ZK proof hợp lệ; cam kết SHA256 bao phủ toàn bộ 32 slot - Chu kỳ quyết toán L1 dừng ở 1 × TX_PUBLIC_INPUT_LENGTH = 1 slot, chỉ xử lý noop - Các "nạp giả" ở gap slot [2..32] được ZK cam kết vào Merkle root mới → số dư L2 của kẻ tấn công tăng 5 × 31N 4) Relay B xử lý Rollup #13282–#13283 tương tự (2 lần), cộng thêm 2 × 31N vào số dư L2. Kết thúc pha mint, tài khoản L2 của kẻ tấn công tích lũy tổng 7 × 31N "deposit không được hậu thuẫn", trong khi vault L1 không thay đổi. Pha 2: Rút — đổi số dư L2 phình to thành tài sản L1 (Rollup #13284–#13290, 7 lần) Kẻ tấn công rút toàn bộ số dư L2 thu được ở pha 1 qua bảy rollup rút: - Rollup #13284 (DAI): withdraw() → RollupProcessor chuyển trực tiếp 270.513,054 DAI tới 0x0f18…edd17 - Rollup #13285 (wstETH): chuyển 167,890 wstETH → kẻ tấn công - Rollup #13286 (yvDAI): chuyển 4.873,857 yvDAI → kẻ tấn công - Rollup #13287 (yvWETH, relay C tiếp quản): chuyển 16,570 yvWETH → kẻ tấn công - Rollup #13288 (LUSD): chuyển 9.273,734 LUSD → kẻ tấn công - Rollup #13289 (yvLUSD): chuyển 359,047 yvLUSD → kẻ tấn công - Rollup #13290 (ETH, giao dịch cuối): RollupProcessor chuyển 908,987 ETH qua internal CALL → kẻ tấn công Toàn bộ giao dịch nguyên tử thực thi thành công (gasUsed = 4.513.539); không thể "rollback một phần" ở cấp hợp đồng. Lợi nhuận ròng khoảng 2,19 triệu USD, rút trực tiếp từ pool tài sản hợp pháp của người dùng trong RollupProcessor. Theo dõi dòng tiền Theo truy vết on-chain (tính đến 15/06/2026, khoảng một ngày sau sự cố): - Tất cả tài sản được chuyển trong một giao dịch từ RollupProcessor qua hợp đồng trung gian 0x06f585…d0fcD thẳng tới EOA 0x0F18D8b44a740272f0be4d08338d2b165b7EdD17. - Hợp đồng trung gian không giữ số dư còn lại. - Toàn bộ tiền bị đánh cắp vẫn còn nguyên trong EOA của kẻ tấn công, chưa ghi nhận hoạt động rửa tiền. Kết luận và khuyến nghị Bài học trọng tâm: giới hạn trên của vòng lặp quyết toán trong hợp đồng ZKRollup phải khớp chặt với phạm vi public input mà ZK proof đã cam kết. Khi tồn tại khoảng trống giữa biên numRealTxs ở lớp hợp đồng L1 và decoded_slots của cam kết SHA256, mọi giả định an toàn dựa vào ZK circuit để ép ràng buộc lên gap slot có thể bị vô hiệu. L1 cần tự xác minh độc lập từng public input slot đã được ZK proof cam kết, không thể "ủy thác" trách nhiệm này cho tầng circuit. Đội ngũ SlowMist khuyến nghị các dự án thực hiện kiểm toán bảo mật độc lập, toàn diện trước khi triển khai hệ thống Rollup, tập trung vào tính nhất quán logic tại ranh giới trạng thái L1/L2, biên tin cậy khi giải mã calldata, và cơ chế xác minh bổ sung on-chain đối với ZK public inputs. Với các hợp đồng đã bị khai tử nhưng còn giữ tài sản lịch sử, cần tổ chức di chuyển hoặc hủy tài sản có kiểm soát để loại bỏ rủi ro phơi nhiễm kéo dài. Bài viết do nhóm Threat Intelligence của SlowMist thực hiện, sử dụng MistEye Threat Intelligence System, nền tảng MistTrack và phân tích dựa trên SlowMist Agent AI. Nếu có câu hỏi hoặc phản hồi, vui lòng liên hệ.