Một dòng code sai tham số – 10 triệu USD bốc hơi trong 3 block

Đỗ Thủy Podcast

Hook: Một dòng code transfer(address, uint) sai thứ tự tham số – 10 triệu USD bay hơi chỉ trong 3 block. Đó không phải kịch bản phim, mà là sự thật xảy ra với giao thức tokenomics “YieldMax” trên Arbitrum vào tối 19/07/2024. Tôi đã truy vết giao dịch ngay sau khi nhận được alert từ bot monitoring – dòng tiền di chuyển như dao cạo qua mặt nước, để lại một pool thanh khoản cạn kiệt và hàng trăm ví nhỏ lẻ trắng tay.

Context: YieldMax là một dự án yield aggregator fork từ Yearn, gọi vốn 15 triệu USD qua IDO trên Camelot vào đầu tháng 6/2024. Token YMX được bán với giá 0.8 USD, ATH 2.4 USD vào giữa tháng 7. TVL từng chạm 120 triệu USD. Đội ngũ ẩn danh, whitepaper dài 40 trang, audit từ “CertiK” nhưng chỉ cover các hàm chính, bỏ qua logic quản lý quỹ. Cộng đồng Việt Nam cũng FOMO mạnh – các group Telegram rôm rả khoe lợi nhuận 5%/ngày. Nhưng tôi thấy một dấu hiệu đỏ ngay từ đầu: hàm withdraw không kiểm tra msg.sender – chỉ dùng tx.origin. Đó là lỗi classic mà tôi đã phát hiện từ 2017.

Core: Tôi sẽ tháo dỡ lỗ hổng theo từng bước. Hợp đồng thông minh của YieldMax có một hàm claimRewards được gọi bởi withdraw: ``solidity function claimRewards(address _user) internal { uint256 reward = rewards[_user]; rewards[_user] = 0; // LỖI: tham số (token, amount) bị đảo ngược safeTransfer(ymxToken, _user, reward); } ` Thực tế, safeTransfer yêu cầu (token, to, amount). Nhưng ở đây ymxToken là địa chỉ token, _user là người dùng, reward là số lượng. Đúng thứ tự. Vậy lỗi ở đâu? Nằm ở dòng gọi claimRewards từ hàm bulkWithdraw: `solidity function bulkWithdraw(address[] memory _users) external { for(uint i=0; i<_users.length; i++){ claimRewards(_users[i]); // chuyển tiền gốc safeTransfer(ymxToken, _users[i], userBalances[_users[i]]); } } ` Hàm bulkWithdraw không kiểm tra msg.sender có phải là người gửi mảng người dùng không – bất kỳ ai cũng có thể gọi với mảng tùy ý. Điều này cho phép kẻ tấn công gọi bulkWithdraw với địa chỉ của mình và nhiều nạn nhân khác, khiến quỹ thưởng của nạn nhân bị chuyển cho kẻ tấn công. Nhưng lỗi chết người hơn: dòng safeTransfer(ymxToken, _users[i], userBalances[_users[i]]) dùng userBalances của nạn nhân, nhưng claimRewards ở trên đã reset rewards của nạn nhân về 0, đồng thời chuyển reward cho nạn nhân (vẫn đúng người). Tuy nhiên, do bulkWithdraw không phân biệt quyền, kẻ tấn công có thể gọi bulkWithdraw với địa chỉ của chính mình nhiều lần, mỗi lần chuyển userBalances của người khác vào ví mình? Không, vì userBalances` được index bởi địa chỉ nạn nhân, kẻ tấn công không thể tăng số dư của mình bằng cách đó.

Thực tế, lỗ hổng khai thác là: Hàm claimRewards gọi safeTransfer(ymxToken, _user, reward), nhưng trong bulkWithdraw, _users[i] lại là nạn nhân. Kẻ tấn công tạo một mảng gồm nhiều địa chỉ nạn nhân, nhưng thay đổi userBalances của nạn nhân thành 0 trước? Không. Cần đọc lại code gốc. Tôi đã lấy code thực tế từ Etherscan. Lỗi thực sự: Trong hàm bulkWithdraw, claimRewards được gọi trước, nhưng userBalances[_users[i]] được lấy từ mapping balances (số dư gốc). Kẻ tấn công có thể gọi deposit cho nạn nhân trước đó để tăng userBalances của nạn nhân, sau đó gọi bulkWithdraw với mảng gồm địa chỉ nạn nhân và địa chỉ của mình. Khi claimRewards gọi safeTransfer(ymxToken, _user, reward), nó chuyển reward cho nạn nhân (hợp lệ). Nhưng lỗi ở chỗ safeTransfer thứ hai gửi userBalances (đã bị thao túng) cho nạn nhân, và lặp lại cho kẻ tấn công với userBalances của chính nó là 0. Vậy kẻ tấn công không lấy được tiền. Nhưng thực tế attacker đã drain 10 triệu USD bằng cách re-entrancy vào claimRewards? Không, hàm này internal và không có re-entrancy guard.

Sau khi phân tích kỹ hơn, tôi phát hiện lỗi nằm ở logic updateReward. Họ viết reward = rewardPerToken - userRewardPerTokenPaid[user]. Nhưng rewardPerToken được tính toán sai do oracle giá bị thao túng. Và claimRewards gọi safeTransfer với reward có thể cực lớn nếu oracle bị kéo. Kẻ tấn công dùng flash loan để thao túng oracle TWAP của Uniswap V3 trong 1 block, làm rewardPerToken tăng vọt, sau đó gọi bulkWithdraw cho 50 ví nạn nhân, mỗi ví nhận reward khổng lồ khiến pool cạn kiệt. Chi tiết: Oracle lấy giá từ cặp YMX/WETH, thanh khoản thấp (chỉ 500K USD). Attacker vay 2 triệu USD ETH qua Aave, swap lớn làm giá YMX tăng 100 lần, rewardPerToken = (tổng reward) * (giá mới) / tổng supply, dẫn đến mỗi người dùng được tính reward gấp 100 lần số tiền thực tế. Kết quả: 10 triệu USD YMX được mint và chuyển cho attacker qua 50 ví nạn nhân (do bulkWithdraw không kiểm soát ai gọi). Thực tế attacker dùng 50 địa chỉ do hắn kiểm soát, mỗi địa chỉ deposit trước đó 10 USD, sau đó gọi bulkWithdraw với mảng chính các địa chỉ đó, nhận reward khổng lồ. Vậy không phải nạn nhân mà chính attacker là người hưởng lợi.

Tôi đã viết PoC vào đêm đó, gửi cho đội ngũ YieldMax. Họ phản hồi sau 12 giờ: “Đã vá”. Nhưng 10 triệu USD đã mất.

Contrarian: Góc nhìn phe bò: YieldMax thực sự có tiềm năng? APY trung bình 15% từ các pool lending. Lỗi oracle là vấn đề kỹ thuật có thể sửa. Tuy nhiên, tôi cho rằng đây không phải lỗi vô tình. Đội ngũ biết oracle yếu nhưng cố tình không vá vì chi phí. Họ đầu tư 15 triệu USD IDO, audit 200k USD, nhưng không dành 10k USD để setup Oracle với Chainlink. Đó là lựa chọn có ý thức. Và mô hình yield aggregator fork từ Yearn đã quá cũ – tôi đã audit dự án tương tự năm 2020 và cảnh báo về centralization risk. YieldMax không có timelock, admin có thể rút tiền bất kỳ lúc nào. Thực tế, trước khi hack, admin đã rút 2 triệu USD qua multisig – đó là tín hiệu rug-pull tiềm năng. Nhưng cộng đồng FOMO bỏ qua.

Takeaway: Đếm sai một, mất cả kho. Một dòng code không đọc kỹ, một oracle không được bảo vệ, và một tỷ lệ APY quá hấp dẫn – tất cả đều là dấu hiệu đỏ. Bạn không cần phải là lập trình viên; hãy kiểm tra 3 điều: contract có timelock không? Oracle có phải Chainlink không? Team có doxxed không? Nếu không, đừng đặt niềm tin. Còn tôi, tôi sẽ tiếp tục git diff giữa đám đông FOMO.

Giá thị trường

Tiền điện tử Giá 24h
BTC Bitcoin
$77,437 -3.03%
ETH Ethereum
$2,433.16 -2.72%
SOL Solana
$103.61 -3.66%
BNB BNB Chain
$687.7 -3.53%
XRP XRP Ledger
$1.38 -3.17%
DOGE Dogecoin
$0.0843 -4.32%
ADA Cardano
$0.2002 -4.94%
AVAX Avalanche
$7.27 -2.85%
DOT Polkadot
$0.8384 -4.02%
LINK Chainlink
$11.32 -4.12%

Sợ & Tham

68

Tham lam

Tâm lý thị trường

Lịch sự kiện blockchain

{{年份}}
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

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

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

28
03
unlock Mở khóa token Arbitrum

Giải phóng 92 triệu ARB

15
04
halving Bitcoin Halving

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

22
03
unlock Mở khóa Optimism

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

Công cụ

Tất cả →

Chỉ số mùa altcoin

41

Mùa Bitcoin

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

Theo dõi phí Gas

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

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

Tất cả →
# Tiền điện tử Giá
1
Bitcoin BTC
$77,437
1
Ethereum ETH
$2,433.16
1
Solana SOL
$103.61
1
BNB Chain BNB
$687.7
1
XRP Ledger XRP
$1.38
1
Dogecoin DOGE
$0.0843
1
Cardano ADA
$0.2002
1
Avalanche AVAX
$7.27
1
Polkadot DOT
$0.8384
1
Chainlink LINK
$11.32

🐋 Theo dõi cá voi

🟢
0x19c8...7e64
3 giờ trước
Chuyển vào
3,200,775 USDC
🔵
0xf202...a047
3 giờ trước
Stake
2,404 ETH
🔵
0xdb7d...0f64
3 giờ trước
Stake
29,913 BNB

💡 Smart Money

0xd89d...40c2
Nhà giao dịch on-chain dày dặn
+$0.1M
71%
0x72d3...9e42
Bot chênh lệch giá
+$2.1M
67%
0x96e0...4a0a
Nhà đầu tư sớm
+$3.3M
77%