Tuần trước, một giao thức lending mới trên Arbitrum vừa huy động 15 triệu USD từ các quỹ đầu tư lớn. Trong vòng 72 giờ sau khi mainnet ra mắt, tôi đã phát hiện một lỗ hổng reentrancy cổ điển trong hợp đồng withdraw() của họ. Bytecode không bao giờ nói dối: chỉ cần một cuộc gọi callback độc hại, kẻ tấn công có thể rút toàn bộ thanh khoản của pool. Đây không phải là lỗi zero-day phức tạp, mà là lỗi mà tôi đã thấy từ năm 2017.
Bối cảnh kỹ thuật
Giao thức này sử dụng mô hình cToken tương tự Compound, nhưng với một twist: họ cho phép người dùng vay nhiều tài sản cùng lúc thông qua một hàm flashWithdraw(). Hàm này gọi _redeem() bên trong, sau đó chuyển token ERC-20 đến người dùng trước khi cập nhật số dư nội bộ. Đây là công thức kinh điển cho reentrancy. Khi tôi đọc mã nguồn Solidity, dòng 187-192 có vẻ vô hại: require(balanceOf[msg.sender] >= amount); _burn(msg.sender, amount); token.transfer(msg.sender, amount);. Nhưng nếu token là một hợp đồng độc hại có fallback, nó có thể gọi lại withdraw() trước khi _burn hoàn tất.

Tôi đã chạy thử nghiệm fuzz với Foundry. Kết quả: chỉ sau 3 lần lặp, tôi tái tạo được reentrancy với 200 ETH ảo. Audit? Tôi thích fuzz testing hơn. Báo cáo audit của giao thức này do một công ty nổi tiếng thực hiện, nhưng họ bỏ qua trường hợp callback từ token ERC-777 vì cho rằng “không phải vector tấn công phổ biến”. Sai lầm chết người.
Core Insight: Vấn đề không nằm ở reentranny đơn thuần
Điều thú vị là lỗ hổng này không chỉ cho phép rút cạn pool, mà còn cho phép kẻ tấn công thao túng oracle giá. Vì giao thức dùng Chainlink làm nguồn giá, khi reentrancy xảy ra, pool tạm thời ở trạng thái không nhất quán, và oracle có thể trả về giá thấp hơn thực tế. Tôi đã viết một PoC bằng Rust sử dụng ethers-rs, mô phỏng cuộc tấn công kết hợp reentrancy + oracle manipulation. Kết quả: có thể liquidate toàn bộ vị thế của người dùng khác trong cùng một giao dịch, thu lợi hơn 500 ETH.
Đây là điểm mù bảo mật mà tôi gọi là “reentrancy 2.0”. Nó không chỉ ảnh hưởng đến số dư, mà còn ảnh hưởng đến toàn bộ hệ thống phụ thuộc vào trạng thái. Các giao thức DeFi hiện đại thường dùng cross-function reentrancy, nơi một hàm gọi nhiều hợp đồng khác nhau. Nếu không có mutex hoặc reentrancy guard ở tất cả các điểm chạm, rủi ro là rất lớn.
Contrarian Angle: Tại sao audit truyền thống thất bại?
Nhiều người cho rằng audit là “tấm vé vàng” cho bảo mật. Nhưng từ kinh nghiệm của tôi, các audit thường kiểm tra từng hợp đồng riêng lẻ, không kiểm tra toàn bộ luồng giao dịch. Lỗ hổng reentrancy trong trường hợp này chỉ xuất hiện khi kết hợp với token ERC-777 và callback. Các auditor thường chỉ kiểm tra ERC-20 tiêu chuẩn, nơi không có fallback. Họ quên rằng thực tế on-chain có hàng triệu token tùy chỉnh với logic riêng.

Hơn nữa, audit thường dựa trên danh sách kiểm tra (checklist) cố định. Reentrancy đã được biết đến từ năm 2016, nhưng các biến thể mới như “cross-contract reentrancy” vẫn bị bỏ qua. Tôi đã thấy ít nhất 5 giao thức trong năm 2025 có cùng lỗ hổng. Bytecode không bao giờ nói dối, nhưng con người thì hay mắc sai lầm.
Takeaway: Dự báo lỗ hổng
Trong 6 tháng tới, tôi dự đoán sẽ có ít nhất 3 vụ tấn công reentrancy quy mô lớn trên các giao thức DeFi mới, đặc biệt là những giao thức sử dụng cross-chain messaging. Các bridge và layer-2 là mục tiêu hàng đầu vì chúng có nhiều điểm gọi hàm không đồng bộ. Nếu bạn là developer, hãy dùng OpenZeppelin’s ReentrancyGuard ở mọi hàm external, và đừng quên kiểm tra token callback. Còn nếu bạn là trader, hãy tránh xa các giao thức chưa có history về fuzz testing.
Bài học từ năm 2017 vẫn còn nguyên giá trị: reentrancy vẫn là kẻ thù số một. Đừng để TVL hào nhoáng đánh lừa bạn. Hãy đọc code, chạy thử, và đặt câu hỏi. Tôi sẽ theo dõi sát sao dự án này và cập nhật trong bài viết tới.