Đào mã thấy lỗi, im lặng là vàng.
Hôm qua, tôi đào sâu vào hợp đồng của một giao thức lending mới nổi. Một dòng code nhỏ khiến tôi dừng lại.
Hàm updatePrice() gọi oracle feed mỗi 10 phút. Nhưng logic thanh lý lại dùng giá tức thời. Một khe hở 600 giây.
Đủ cho kẻ tấn công thao túng thị trường.
Context: Cơ chế Oracle trong DeFi
Hầu hết các giao thức lending (Aave, Compound, v.v.) dùng Chainlink làm nguồn giá chính. Chainlink duy trì các node oracle cập nhật giá trên chuỗi mỗi vài phút.
Nhưng đây là vấn đề: giá trên chuỗi luôn là quá khứ. Trong thị trường biến động mạnh, chênh lệch giữa giá thực tế và giá oracle có thể lên đến 5-10%.
Giao thức tôi audit dùng cơ chế thanh lý dựa trên tỷ lệ tài sản thế chấp (collateral ratio). Khi giá giảm nhanh, hệ thống cần thanh lý ngay lập tức. Nhưng nếu oracle chậm 10 phút, kẻ tấn công có thể vay nặng lãi, rút thanh khoản, và để lại khoản nợ xấu.
Đây không phải lỗi lý thuyết. Năm 2023, sự kiện Euler Finance mất 197 triệu USD vì chính kịch bản này. Oracle feed bị thao túng thông qua một flash loan, nhưng gốc rễ là độ trễ cập nhật.
Core: Phân tích kỹ thuật chi tiết
Tôi mở mã nguồn của giao thức X. Hợp đồng chính LendingPool.sol có hàm liquidate().
function liquidate(address borrower) external {
uint256 price = oracle.getPrice(address(collateral));
uint256 debt = getDebt(borrower);
uint256 collateralValue = getCollateralValue(borrower, price);
require(collateralValue < debt * 1.1 ether / 1e18, "Not undercollateralized");
// ... thanh lý
}
Oracle getPrice() trả về giá từ một aggregator được cập nhật mỗi 10 phút. Nhưng không có kiểm tra thời gian cập nhật (heartbeat).
Tôi tính toán: Trong 10 phút, với thanh khoản thấp, giá một token có thể giảm 15%. Kẻ tấn công có thể: 1. Flash loan một lượng lớn token. 2. Dùng token làm tài sản thế chấp, vay stablecoin. 3. Bán token trên sàn DEX, đẩy giá xuống. 4. Oracle vẫn giữ giá cũ. 5. Hệ thống cho phép thanh lý vị thế của chính kẻ tấn công (vì giá oracle cao hơn giá thị trường). 6. Kẻ tấn công mua lại tài sản thế chấp với giá thấp, trả nợ, và giữ chênh lệch.
Đây là tấn công Oracle Latency + Self-Liquidation.
Tôi kiểm tra thêm: Giao thức có cơ chế grace period không? Không. Có circuit breaker không? Không.
Kết quả: Một lỗ hổng critical. Tôi báo cáo ngay lập tức.
Contrarian: Điểm mù bảo mật mà hầu hết auditor bỏ qua
Nhiều người cho rằng lỗi oracle là do giá sai. Sai. Lỗi thực sự là thiếu kiểm tra độ tươi của dữ liệu.
Chainlink cung cấp latestTimestamp() và latestRound(). Nhưng ít giao thức dùng chúng. Họ chỉ đọc latestAnswer().
Một lỗi tinh vi hơn: Khi oracle bị tạm dừng (do gas thấp hoặc lỗi node), giá cũ vẫn được dùng. Giao thức không fallback về nguồn giá thứ hai.
Tôi đã thấy điều này trong audit của mình. Các đội phát triển thường tập trung vào logic vay/trả, mà quên mất rằng oracle là điểm tập trung hóa thực sự.
Thanh khoản cạn, bẫy còn đó.
Takeaway: Dự báo lỗ hổng tương lai
Trong 6 tháng tới, tôi dự đoán sẽ có ít nhất 3 vụ tấn công lớn nhắm vào oracle độ trễ. Các giao thức lending mới, đặc biệt là trên L2 với block time nhanh, sẽ dễ bị tổn thương nhất.
Giải pháp? Không phải chờ Chainlink nhanh hơn. Mà là: - Dùng nhiều oracle cùng lúc (median). - Thêm circuit breaker khi giá thay đổi đột ngột. - Bắt buộc kiểm tra latestTimestamp() trong mọi hàm quan trọng.
Bạn có tin rằng giao thức mình đang dùng có kiểm tra độ tươi của oracle không?
Tôi thì không.