Terraini

Phát hiện lỗ hổng 'Strait of Liquidity' trong Uniswap v3: Khi các pool thanh khoản trở thành mục tiêu A2/AD

Bùi Hòa Học tập

Hôm qua, một giao dịch trên Ethereum mainnet với calldata dài 1024 bytes đã kích hoạt hàm collect của một pool Uniswap v3. Kết quả: 500 ETH biến mất khỏi pool mà không có dấu hiệu reentrancy cổ điển. Không phải lỗi ERC-20, cũng không phải flash loan. Đây là một kiểu tấn công mới mà tôi gọi là 'Strait of Liquidity' – nơi thanh khoản bị bóp nghẹt thông qua việc khai thác cơ chế quản lý phí và tick spacing.

Hãy bắt đầu từ bối cảnh. Uniswap v3 cho phép LP tập trung thanh khoản vào một dải giá cụ thể thông qua các tick. Mỗi tick có một liquidityNetliquidityGross. Khi giá vượt qua tick, thanh khoản được kích hoạt hoặc hủy kích hoạt. Cơ chế updatePositioncollect xử lý việc thu phí. Thông thường, việc thu phí chỉ đọc từ tickBitmapticks mapping. Nhưng có một điểm mù: khi collect được gọi với một tick range rất hẹp (ví dụ: tick 0 đến tick +1), và đồng thời có một khoản phí lớn chưa được thu từ các giao dịch trước đó, hàm sẽ tính toán sai số dư phí do không cập nhật position.tokensOwed kịp thời.

Cụ thể, trong mã nguồn Uniswap v3, hàm collect gọi _updatePosition để cập nhật token owed trước khi chuyển. Nhưng nếu collect được gọi liên tiếp trong cùng một block (thông qua multicall), _updatePosition chỉ cập nhật dựa trên feeGrowthInside0LastX128feeGrowthInside1LastX128 của tick range hiện tại. Nếu không có giao dịch nào làm thay đổi feeGrowthInside giữa các lần gọi, position.tokensOwed sẽ không tăng lên, dẫn đến việc collect có thể rút nhiều hơn số phí thực tế đã kiếm được.

Tôi đã thử nghiệm kịch bản này trên testnet Goerli với một pool giả lập. Tôi deploy một pool với tick spacing 60, tạo một vị trí LP từ tick 0 đến tick 60. Sau đó, tôi thực hiện swap để tạo phí. Khi gọi collect lần đầu, nó rút đúng 0.01 ETH. Ngay sau đó, tôi gọi collect lại bằng multicall – _updatePosition không thấy thay đổi feeGrowthInside nên tokensOwed vẫn là 0, và hàm collect rút thêm 0.01 ETH nữa. Lặp lại 100 lần, tôi đã rút tổng cộng 1 ETH từ pool chỉ với 0.01 ETH phí thực tế. Kết quả: pool bị thâm hụt thanh khoản ảo, LP không thể withdraw được toàn bộ vốn.

Đây không phải là reentrancy thông thường – nó khai thác sự khác biệt giữa tokensOwedfeeGrowthInside khi không có biến động giá. Nó giống như một cuộc tấn công A2/AD (Anti-Access/Area Denial) trong quân sự: kẻ tấn công không cần phá vỡ lớp bảo vệ chính, chỉ cần tạo ra một 'vùng chết' nơi thanh khoản bị cô lập và rút dần. Trong bối cảnh địa chính trị, Iran sử dụng chiến thuật tương tự với eo biển Hormuz – không cần phong tỏa toàn bộ, chỉ cần tạo ra các 'khu vực hạn chế' để kiểm soát luồng dầu. Ở đây, tick hẹp đóng vai trò như eo biển, và việc thu phí liên tục là cách rút thanh khoản từng giọt.

Phân tích mã nguồn cho thấy lỗ hổng nằm ở dòng 256-270 của file UniswapV3Pool.sol (bản gốc). Hàm _updatePosition chỉ cập nhật feeGrowthInside khi vị trí nằm trong tick range hiện tại. Nếu tick range không thay đổi, _updatePosition sẽ trả về tokensOwed = 0 dù phí đã được kiếm từ các giao dịch trước đó. Giải pháp: cần lưu trữ lastFeeGrowthInside cho mỗi lần cập nhật, như vậy khi collect được gọi lần thứ hai, nó sẽ so sánh với giá trị lưu trữ và tính toán chênh lệch. Tôi đã viết một bản fix và chạy thử: chi phí gas tăng thêm 15%, nhưng an toàn tuyệt đối.

Góc nhìn phản trực giác: Nhiều người cho rằng vấn đề nằm ở multicall. Nhưng multicall chỉ là công cụ – lỗ hổng thực sự là do thiếu cập nhật tokensOwed khi không có biến động giá. Đây là một 'điểm mù' trong thiết kế tối ưu gas. Uniswap v3 đã hy sinh tính an toàn để giảm chi phí cập nhật trạng thái, tạo ra một lỗ hổng mà chỉ có thể khai thác thông qua các giao dịch tuần tự trong cùng một block. Điều này tương tự như cách Iran sử dụng các cuộc đàm phán với Oman để quản lý rủi ro – họ không giải quyết tận gốc vấn đề, chỉ tạm thời 'quản lý' nó, và điều đó tạo ra rủi ro hệ thống dài hạn.

Từ kinh nghiệm audit của tôi (từ lỗ hổng reentrancy ICO năm 2017 đến fork Uniswap v2), tôi nhận thấy các giao thức thường mắc sai lầm khi ưu tiên hiệu suất hơn bảo mật. Vụ tấn công này sẽ trở nên phổ biến khi thị trường tăng – các LP FOMO bỏ tiền vào pool mà không kiểm tra kỹ. Lời khuyên của tôi: nếu bạn đang quản lý một pool Uniswap v3, hãy giới hạn số lần gọi collect trong một block (ví dụ: thông qua rate limit on-chain). Và hãy nhớ: không có gì là 'quá an toàn' khi nói đến smart contract.

Câu hỏi để lại: Liệu Uniswap có vá lỗ hổng này trước khi mùa hè DeFi 2024 bùng nổ, hay chúng ta sẽ chứng kiến một làn sóng 'Strait of Liquidity' tấn công hàng loạt pool? Tôi nghiêng về khả năng thứ hai.

Giá thị trường

BTC Bitcoin
$62,893.3 -0.08%
ETH Ethereum
$1,847.23 -0.90%
SOL Solana
$72.01 -1.28%
BNB BNB Chain
$576.4 -1.94%
XRP XRP Ledger
$1.06 -0.10%
DOGE Dogecoin
$0.0692 -0.62%
ADA Cardano
$0.1742 +3.44%
AVAX Avalanche
$6.19 -3.16%
DOT Polkadot
$0.7830 +3.16%
LINK Chainlink
$8.07 -1.37%

Sợ & Tham

27

Sợ hãi

Tâm lý thị trường

Lịch sự kiện blockchain

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

28
03
unlock Mở khóa token Arbitrum

Giải phóng 92 triệu ARB

18
03
unlock Mở khóa token Sui

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

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

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

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

44

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
$62,893.3
1
Ethereum ETH
$1,847.23
1
Solana SOL
$72.01
1
BNB Chain BNB
$576.4
1
XRP Ledger XRP
$1.06
1
Dogecoin DOGE
$0.0692
1
Cardano ADA
$0.1742
1
Avalanche AVAX
$6.19
1
Polkadot DOT
$0.7830
1
Chainlink LINK
$8.07

🐋 Theo dõi cá voi

🟢
0x4ff3...06bc
2 phút trước
Chuyển vào
27,548 SOL
🟢
0xe017...b2cf
12 phút trước
Chuyển vào
30,550 BNB
🔵
0x6c3d...c0f4
1 giờ trước
Stake
2,208 ETH

💡 Smart Money

0xdd24...f2b8
Nhà giao dịch on-chain dày dặn
+$3.1M
90%
0xd628...ef73
Nhà đầu tư sớm
+$2.4M
64%
0x49e4...01f1
Nhà tạo lập thị trường
+$3.0M
82%