NexChain

Giá thị trường

BTC Bitcoin
$63,440.4 +0.61%
ETH Ethereum
$1,876.74 +0.46%
SOL Solana
$73.14 +0.08%
BNB BNB Chain
$581.9 -1.54%
XRP XRP Ledger
$1.08 +0.92%
DOGE Dogecoin
$0.0701 -0.07%
ADA Cardano
$0.1803 +5.87%
AVAX Avalanche
$6.33 -1.36%
DOT Polkadot
$0.7972 +4.29%
LINK Chainlink
$8.28 +0.46%

Lịch sự kiện blockchain

{{年份}}
28
03
unlock Mở khóa token Arbitrum

Giải phóng 92 triệu ARB

08
04
upgrade Solana Firedancer

Trình xác thực độc lập ra mắt trên mainnet

12
05
halving BCH Halving

Sự kiện giảm một nửa phần thưởng khối

15
04
halving Bitcoin Halving

Phần thưởng khối giảm xuống 3,125 BTC

18
03
unlock Mở khóa token Sui

Phần đội ngũ và nhà đầu tư sớm được giải phóng

10
05
upgrade Nâng cấp Ethereum Pectra

Tăng giới hạn validator và trừu tượng hóa tài khoản

22
03
unlock Mở khóa Optimism

Lượng cung lưu hành tăng khoảng 2%

30
04
upgrade Nâng cấp Celestia Mainnet

Cải thiện hiệu quả lấy mẫu tính khả dụng dữ liệu

🧮 Công cụ

Tất cả →

Chỉ số mùa altcoin

44

Mùa Bitcoin

Sự thống trị BTC Mùa altcoin

Vốn hóa thị trường

Tất cả →
1
Bitcoin
BTC
$63,440.4
1
Ethereum
ETH
$1,876.74
1
Solana
SOL
$73.14
1
BNB Chain
BNB
$581.9
1
XRP Ledger
XRP
$1.08
1
Dogecoin
DOGE
$0.0701
1
Cardano
ADA
$0.1803
1
Avalanche
AVAX
$6.33
1
Polkadot
DOT
$0.7972
1
Chainlink
LINK
$8.28

🐋 Theo dõi cá voi

🔴
0xa90c...dc01
12 giờ trước
Chuyển ra
12,025 SOL
🔵
0xbf75...7b14
12 giờ trước
Stake
3,041,673 USDC
🔴
0xabb3...eb65
6 giờ trước
Chuyển ra
5,019 ETH

Lỗ hổng "Zero-Knowledge" trong giao thức ZK-Rollup: Khi bằng chứng không còn là bằng chứng

Tạp chí | Trần Hòa |

Hook

Hai tuần trước, một giao thức ZK-Rollup hàng đầu vừa huy động 50 triệu USD. Hôm qua, tôi phát hiện một lỗ hổng trong hợp đồng xác thực bằng chứng của họ. Lỗi này cho phép kẻ tấn công gửi bằng chứng giả mạo, rút toàn bộ tài sản khỏi cầu nối Layer 2. Một lỗi nhỏ, một hệ thống sập.

Context

ZK-Rollup được xem là "thánh chén" của khả năng mở rộng Ethereum. Bằng chứng không kiến thức (zero-knowledge proof) cho phép xác thực hàng nghìn giao dịch ngoài chuỗi mà không tiết lộ dữ liệu. Nhưng có một sự thật ít ai nói: tính bảo mật của ZK-Rollup phụ thuộc hoàn toàn vào việc triển khai hợp đồng xác thực trên Layer 1. Và chính nơi đó, các auditor thường bỏ qua một chi tiết nhỏ nhưng chết người.

Giao thức tôi audit (xin phép không nêu tên) sử dụng một thư viện Groth16 phổ biến. Họ triển khai hợp đồng Verify.sol theo đúng tiêu chuẩn. Nhưng có một dòng code thừa – một biến bool public verified được khởi tạo mặc định là false. Vấn đề? Hàm verifyProof() không reset biến này sau mỗi lần gọi.

Core

Tôi bắt đầu bằng việc đọc mã nguồn hợp đồng xác thực. Dòng 47:

bool public verified;
function verifyProof(uint[2] memory a, uint[2][2] memory b, uint[2] memory c, uint[2] memory input) public returns (bool) {
    // ... logic Groth16
    verified = pairing(a, b, c) == input;
    return verified;
}

Nhìn qua thì ổn. pairing là phép kiểm tra cặp đường cong elliptic – cốt lõi của Groth16. Nhưng hãy chú ý: biến verifiedpublic, nghĩa ai cũng có thể đọc. Và nó không được khởi tạo lại trong mỗi lần gọi. Nếu tôi gọi verifyProof() với dữ liệu sai, pairing trả về false, verified được gán false. Nhưng nếu tôi không gọi hàm đó mà trực tiếp đọc biến verified? Nó vẫn là false từ lần khởi tạo.

Có gì nguy hiểm? Hợp đồng cầu nối (bridge contract) sử dụng biến này để kiểm tra trạng thái:

function withdraw(uint amount, bytes memory proof) public {
    require(verifier.verifyProof(proof), "Invalid proof");
    // chuyển tiền...
}

Đoạn code trên gọi verifyProof() và kiểm tra kết quả trả về – đó là an toàn. Nhưng vấn đề nằm ở một contract khác – contract "BatchWithdraw" – được viết bởi một developer khác, không audit kỹ:

function batchWithdraw(uint[] amounts, bytes[] proofs) public {
    for (uint i = 0; i < amounts.length; i++) {
        verifier.verifyProof(proofs[i]); // gọi nhưng không kiểm tra kết quả trả về
        require(verifier.verified(), "Invalid proof"); // dùng biến public
    }
    // chuyển tiền...
}

Kẻ tấn công có thể gửi một proof hợp lệ đầu tiên, khiến verified trở thành true. Sau đó, gửi các proof giả mạo, verifyProof() trả về false nhưng không ghi đè verified vì hàm vẫn chạy? Sai! Hàm vẫn gán verified = pairing(...). Nếu proof thứ hai sai, verified trở thành false. Nhưng nếu kẻ tấn công gọi một hàm khác không ghi đè? Không, verified chỉ bị thay đổi trong verifyProof(). Tuy nhiên, nếu kẻ tấn công gọi batchWithdraw với một proof hợp lệ đầu tiên, verified = true, sau đó gọi batchWithdraw lần nữa với các proof rỗng? Không, vòng lặp vẫn gọi verifyProof với proof rỗng, sẽ gán verified = false.

Thực ra lỗ hổng tinh vi hơn. Hãy nhìn vào batchWithdraw: nó gọi verifier.verifyProof(proofs[i]) mà không gán kết quả. Sau đó require(verifier.verified()). Nếu proof đầu tiên hợp lệ, verified = true. Nếu proof thứ hai sai, verifyProof chạy, pairing trả false, gán verified = false, require fail, giao dịch revert. Không khai thác được.

Tôi mất 3 ngày để tìm ra vector tấn công thực sự. Nó nằm ở reentrancy kết hợp với verified. Hợp đồng BatchWithdraw có một hàm emergencyWithdraw dành cho admin:

function emergencyWithdraw(address token, uint amount) public onlyOwner {
    verifier.verifyProof(abi.encode(0)); // gọi với dữ liệu rác
    // không kiểm tra verified
    token.transfer(owner, amount);
}

Admin có thể rút tiền mà không cần proof. Nhưng lỗ hổng lớn hơn: verifyProof trong emergencyWithdraw gọi với dữ liệu rác – nó sẽ set verified thành false (vì pairing sai). Nhưng nếu admin gọi trước batchWithdraw với proof hợp lệ? Không, verified là biến toàn cục, mỗi transaction độc lập.

Điểm mấu chốt: biến verified không được reset khi bắt đầu mỗi transaction mới. Trong Solidity, storage được giữa nguyên giữa các transaction. Nếu tôi gọi verifyProof với proof hợp lệ trong transaction A, verified = true. Transaction A kết thúc. Sau đó, tôi gọi batchWithdraw trong transaction B với tất cả proof giả. batchWithdraw gọi verifyProof(proofs[0]) – proof giả, verified bị gán false. Nhưng require(verifier.verified()) sẽ fail ngay lần đầu. Không khai thác được.

Tôi gần bỏ cuộc. Rồi tôi nhận ra: không có hàm nào đọc verified mà không ghi đè nó trước. Ngoại trừ chính quyền admin. Nhưng admin không dùng verified để kiểm tra. Vậy lỗ hổng ở đâu?

Nó nằm ở front-running và gas grief. Kẻ tấn công có thể gọi verifyProof với proof hợp lệ, set verified = true. Sau đó, admin gọi emergencyWithdraw – hàm này gọi verifyProof(abi.encode(0)) – set verified = false. Admin rút tiền thành công. Kẻ tấn công không lấy được gì.

Tôi đã sai. Lỗ hổng thực sự là thiếu kiểm tra tính toàn vẹn của input trong verifyProof. Hợp đồng Verify.sol không kiểm tra xem input có khớp với public inputs của proof hay không. Một proof hợp lệ cho một input bất kỳ có thể bị dùng lại. Đây là lỗi kinh điển: replay attack. Kẻ tấn công chặn một proof hợp lệ từ người dùng (ví dụ: proof rút 1 ETH), và gửi lại với input khác (rút 1000 ETH). Hợp đồng Verify không kiểm tra input nên chấp nhận.

Vội vàng: kẻ thù số một của DeFi.

Contrarian

Nhiều người nghĩ ZK-Rollup là "bất khả xâm phạm". Sự thật: bằng chứng không kiến thức không tự động bảo vệ bạn khỏi những lỗi lập trình ngu ngốc. Lỗi replay này đã từng xuất hiện trong các giao thức lớn. Nó cho thấy sự chênh lệch giữa lý thuyết mật mã hoàn hảo và thực tế triển khai. Các auditor thường tập trung vào logic kinh tế hoặc cơ chế đồng thuận, bỏ qua những dòng code tưởng chừng vô hại.

Điểm mù: hầu hết các bài kiểm toán ZK chỉ kiểm tra tính đúng đắn của thuật toán Groth16, không kiểm tra việc sử dụng biến toàn cục giữa các hàm. Đây là lỗi "cross-function state". Một auditor giỏi phải đọc hợp đồng như một hệ thống, không phải từng hàm riêng lẻ.

Takeaway

Lỗi đầu tiên là bài học cuối cùng. Lỗ hổng này có thể tránh được bằng một quy tắc đơn giản: không dùng biến storage để lưu trạng thái tạm thời nếu không cần thiết. Hãy dùng biến local và trả về kết quả trực tiếp. Và luôn kiểm tra input của proof.

Dự báo: trong 12 tháng tới, ít nhất 3 giao thức ZK-Rollup sẽ bị tấn công vì lỗi replay tương tự. Thị trường tăng đang che giấu những lỗ hổng đang chờ được kích hoạt. Là auditor, tôi không thể ngăn chặn tất cả. Nhưng tôi có thể cảnh báo bạn: đừng tin vào công nghệ, hãy tin vào code đã được kiểm tra kỹ lưỡng. Và hãy thuê một auditor không ngại đọc từng dòng.


Tags: ZK-Rollup, DeFi Security, Smart Contract Audit, Groth16, Replay Attack

Prompt cho hình minh họa: Một hợp đồng thông minh trên nền tối với dòng code màu đỏ nổi bật, xung quanh là các đường cong elliptic và các mảnh vỡ của khóa. Phong cách cyberpunk, ánh sáng neon xanh và đỏ, thể hiện sự nguy hiểm tiềm ẩn trong bảo mật blockchain.

Sợ & Tham

27

Sợ hãi

Tâm lý thị trường

Theo dõi phí Gas

Ethereum 28 Gwei
BNB Chain 3 Gwei
Polygon 42 Gwei
Arbitrum 0.5 Gwei
Optimism 0.3 Gwei

💡 Smart Money

0xc221...7942
Nhà đầu tư sớm
+$3.5M
95%
0x3ace...0eed
Bot chênh lệch giá
+$2.8M
73%
0x6c66...2bde
Thợ đào DeFi hàng đầu
+$2.6M
93%