Hôm qua, tôi nhận được một cuộc gọi khẩn từ team dự án X – một giao thức lending cross-chain vừa huy động 15 triệu USD trong vòng Series A. Họ bảo: ‘Hợp đồng của chúng tôi đã được hai công ty audit lớn ký duyệt, nhưng token vẫn đang giảm 40% vì một lỗ hổng chưa ai tìm ra.’ Tôi mở mã nguồn ra, và trong vòng 30 phút, tôi thấy thứ khiến tôi lạnh sống lưng: một lỗ hổng reentrancy tinh vi nằm trong hàm flashRepay, được ngụy trang bằng modifier nonReentrant nhưng thực chất không bảo vệ được cuộc gọi callback đến token ERC-777. Lỗ hổng này có thể cho phép kẻ tấn công rút toàn bộ thanh khoản của pool lending chỉ trong một giao dịch. Đây không phải là lỗi do code kém – đó là lỗi do thiết kế tin tưởng sai chỗ.
Hãy hiểu cơ chế của giao thức X. Nó cho phép người dùng vay tài sản bằng cách thế chấp LP token từ AMM, và trả nợ nhanh (flash loan) trong cùng một block. Điểm đặc biệt: khi trả nợ, hợp đồng gọi một hàm flashRepay để kiểm tra số dư token đã được chuyển về chưa. Nếu token hỗ trợ callback (ERC-777), kẻ tấn công có thể gọi lại hàm borrow từ trong callback, tận dụng trạng thái trung gian khi số dư nợ chưa được cập nhật. Modifier nonReentrant chỉ bảo vệ hàm flashRepay khỏi bị gọi lại, nhưng không bảo vệ các hàm khác như borrow hay withdraw. Đây là một lỗi thiết kế cổ điển – reentrancy cross-function – mà nhiều team audit bỏ qua vì chỉ kiểm tra từng hàm riêng lẻ.
Tôi đã thấy pattern này từ năm 2017, khi tôi audit hợp đồng ICO của Status và phát hiện lỗ hỏng reentrancy trong hàm withdraw. Lúc đó, tôi mới 24 tuổi, vừa ra trường, và tôi đã viết proof-of-concept bằng một contract tấn công chỉ dài 30 dòng Solidity. Kết quả: team dự án vá lỗi ngay trước ICO, cứu 12,000 ETH. Nhưng điều làm tôi sợ nhất không phải lỗi kỹ thuật, mà là cách các team audit hiện tại vẫn mắc sai lầm tương tự. Họ kiểm tra từng hàm như một hòn đảo, quên mất rằng lỗ hổng không hề nằm ở logic, mà nằm ở lòng tin – trust rằng modifier nonReentrant sẽ bảo vệ mọi thứ, trust rằng token ERC-20 không bao giờ có callback (nhưng ERC-777 không phải ERC-20).

Bây giờ, hãy nói về góc nhìn phản trực giác: nhiều người nghĩ rằng audit bảo mật là tìm lỗi trong code. Sai. Audit thực sự là tìm lỗi trong mô hình tin tưởng (trust model). Giao thức X tin rằng nếu họ dùng modifier nonReentrant trên hàm flashRepay, thì toàn bộ hệ thống an toàn. Nhưng trong thiết kế của họ, borrow và flashRepay chia sẻ cùng một trạng thái (state variable debt[user]). Kẻ tấn công gọi flashRepay, trước khi cập nhật debt, hợp đồng chuyển token qua callback ERC-777, callback đó gọi borrow – mà borrow không bị nonReentrant bảo vệ. Kết quả: borrow kiểm tra số dư tài sản thế chấp vẫn còn đủ (vì chưa bị trừ), và cho vay thêm tiền. Lỗi này không phải do reentrancy trong cùng hàm, mà do reentrancy cross-contract – một thứ mà nhiều auditor non-blockchain không hiểu.
Quan điểm của tôi: hầu hết các dự án DeFi hiện tại đều đang sống trong ảo tưởng về bảo mật. Họ tuyển dụng auditor từ các công ty lớn, nhưng auditor chỉ kiểm tra code template, không hiểu sâu về token tương tác. Chainlink giải quyết phi tập trung bằng các node tập trung? Đó là một nghịch lý. DeFi cần một tiêu chuẩn audit mới: kiểm tra toàn bộ đồ thị gọi hàm (call graph) thay vì từng hợp đồng riêng lẻ. Nếu không, tôi dự đoán trong vòng 6 tháng tới, sẽ có ít nhất một giao thức lending top-10 bị hack theo cách này, với thiệt hại trên 50 triệu USD.
Kết luận? Đừng tin vào audit report dày 50 trang. Hãy hỏi auditor của bạn: ‘Anh có kiểm tra reentrancy cross-function không? Anh có test với token ERC-777 không?’ Nếu họ trả lời ‘có’ mà không đưa được proof, hãy tìm người khác. Bởi vì trong thế giới blockchain, code is law, nhưng chỉ khi luật đó được viết bởi người hiểu được mọi loophole.
