Bối cảnh
Vào ngày 1 tháng 12 năm 2025, giao thức tổng hợp lợi suất phi tập trung lâu đời Yearn đã bị tấn công, gây thiệt hại khoảng 9 triệu đô la.
Sau đây là phân tích chi tiết về cuộc tấn công này của nhóm bảo mật SlowMist: Nguyên nhân gốc rễ nằm ở chỗ logic hàm (_calc_supply) được sử dụng để tính toán nguồn cung trong hợp đồng Nhóm hoán đổi ổn định có trọng số yETH của Yearn đã sử dụng một phép toán không an toàn, cho phép tràn và làm tròn trong quá trình tính toán. Điều này dẫn đến một sai lệch nghiêm trọng khi tính toán tích của nguồn cung mới và số dư ảo, cuối cùng cho phép kẻ tấn công thao túng thanh khoản đến một giá trị cụ thể và đúc một số lượng token LP bất ngờ để kiếm lời. yETH là một pool Automated Market Maker (AMM) bao gồm nhiều loại Ethereum Liquidity Staking Derivatives (LST). Người dùng có thể gửi LST vào pool để cung cấp thanh khoản và nhận token yETH. Mỗi tài sản LST có một nhà cung cấp tỷ giá hối đoái tương ứng. Số dư tài sản trong pool thanh khoản nhân với lãi suất được gọi là "số dư ảo" (vb), được tính như sau: Hợp đồng theo dõi một biến D, biểu thị tổng nguồn cung LP trong pool thanh khoản ở trạng thái cân bằng hoàn hảo. Bất kỳ sự tăng hoặc giảm giá trị nào của D sẽ tương ứng phát hành hoặc hủy một lượng token LP (yETH) tương ứng. Cơ chế này đảm bảo neo 1:1, được tính như sau: (Hình ảnh của hình ảnh với σ và π) trong đó σ là tổng số dư ảo của tất cả tài sản (vb_sum), và π là tổng tích của các số dư ảo (vb_prod). Tổng tích của các số dư ảo (vb_prod) được cập nhật đồng bộ khi tính toán nguồn cung mới, được tính như sau: [Hình ảnh tấn công/phân tích] Giao dịch tấn công: 0x53fe7ef190c34d810c50fb66f0fc65a1ceedc10309cf4b4013d64042a0331156
1. Đầu tiên, kẻ tấn công đã vay một lượng lớn tài sản LST thông qua các khoản vay nhanh, bao gồm (wstETH, rETH, WETH, 0xa35b_ETHx, rETH, wstETH và cbETH).

Lưu ý rằng một phần WETH đã được chuyển đổi thành ETH và gửi vào Tornado Cash. Sau đó, việc rút tiền từ Tornado Cash đã kích hoạt chức năng dự phòng của hợp đồng tấn công, thực hiện cuộc tấn công:


3. Sau đó, kẻ tấn công thực hiện năm thao tác liên tiếp để loại bỏ và thêm thanh khoản. Khi loại bỏ thanh khoản, yETH đã bị đốt và kẻ tấn công đã mua lại tám tài sản LST trong nhóm theo trọng số của chúng; tuy nhiên, khi thêm thanh khoản, chỉ có thanh khoản cho một vài tài sản này được thêm vào, còn thanh khoản cho cbETH, wOETH và mETH thì không. Sau lần bơm thanh khoản thứ năm, tổng tích số dư ảo (vb_prod) sẽ được cập nhật thành 0, và giá trị cung D sẽ được cập nhật thành một giá trị gần bằng tổng số dư ảo (vb_sum), lớn hơn bình thường. Vậy tại sao lại như vậy? Hãy cùng phân tích hàm `_calc_supply`: Hàm này tính toán nguồn cung mới theo chu kỳ bằng một vòng lặp. Trong mỗi vòng lặp, nó lặp lại 8 lần để tính tích mới cho vòng lặp tiếp theo. Thuật toán tính nguồn cung trong mỗi vòng lặp có thể được đơn giản hóa thành công thức: `s’ = (l - s * r) / d`, và thuật toán tính tích có thể được đơn giản hóa thành: `r’ = r * (s’ / s)^8`. Bằng cách mô phỏng các bước giao dịch bằng foundry, chúng ta có thể xác định rằng nguồn cung hiện tại (_supply) xấp xỉ 2,51e21, tổng số dư ảo (_vb_sum) xấp xỉ 1,0903e22, tích số dư ảo (_vb_prod) xấp xỉ 3,5e15 và _amplification là một giá trị hằng số 4,5e20. Trong vòng lặp đầu tiên, s’ = ((4.5e20 * 1.0903e22) - 2.51e21 * 3.5e15) / (4.5e20 - 1e18) ≈ 1.0927e22, r’ = 3.5e15 * (1.09e22 / 2.5e21)^8 ≈ 4.57e20. Trong vòng lặp thứ hai, s’ = ((4.5e20 * 1.0903e22) - 1.0927e22 * 4.57e20) / (4.5e20 - 1e18) ≈ 1.94e18. Lỗ hổng cốt lõi kích hoạt cuộc tấn công này là khi tính toán nguồn cung mới, tử số sử dụng hàm `unsafe_sub` để trừ các giá trị của `l` và `s * r`. Hàm này không kiểm tra tràn, dẫn đến giá trị tính toán cuối cùng bị tràn xuống còn 1,94e18, ít hơn nhiều so với nguồn cung trước đó. Hơn nữa, khi tính toán sản phẩm mới `r'`, vì `s'` giờ nhỏ hơn nhiều so với `s`, nên tổng sản phẩm số dư ảo mới được tính toán (`vb_prod`) sẽ được làm tròn xuống 0. Vì sản phẩm mới r’ được làm tròn thành 0, giá trị tính toán nguồn cung trong tất cả các vòng lặp tiếp theo sẽ luôn bằng một giá trị cố định: s’ = ((4,5e20 * 1,0903e22) - 0) / (4,5e20 - 1e18) ≈ 1,0927e22. Tổng nguồn cung LP được tính toán cuối cùng (khoảng 1,0927e22) và sản phẩm số dư ảo (bằng 0) sẽ được cập nhật trong kho lưu trữ hợp đồng và yETH bị lệch sẽ được đúc cho kẻ tấn công. Tiếp theo, với sản phẩm số dư ảo và tổng cung bị thao túng, kẻ tấn công lại thêm thanh khoản một chiều bằng cbETH, khiến hai lần bổ sung thanh khoản này tạo ra một lượng yETH bất ngờ cho kẻ tấn công. 4. Sau đó, kẻ tấn công khôi phục tổng sản phẩm (vb_prod) của số dư ảo về một giá trị khác không bằng cách loại bỏ thanh khoản (số tiền 0). Trước tiên, hãy theo dõi hàm remove_liquidity: Chúng ta có thể thấy rằng ngay cả khi số lượng LP được truyền vào là 0, tổng sản phẩm số dư ảo vẫn được tính toán lại và cập nhật khi thanh khoản bị loại bỏ. Công thức tính toán là: Trong đó D là giá trị tổng cung, wi là trọng số của từng tài sản và vbi là số dư ảo mới sau khi trừ đi số tiền chuộc lại cho từng tài sản. Vì số lượng LP được truyền vào là 0, nên nó vẫn bằng số dư ảo trước đó. Tổng tích số dư ảo được tính toán cuối cùng (vb_prod) xấp xỉ Trong 9.09e19, tổng số dư ảo và tổng cung vẫn không đổi, vẫn bằng các giá trị sau lần cộng thanh khoản một chiều cuối cùng (vb_sum ≈ 1.0926e22, cung ≈ 1.095e22). Tiếp theo, hàm `update_rates` được gọi để cập nhật tỷ giá hối đoái của tài sản wOETH. Tiếp theo, trong hàm `update_rates`: Đầu tiên, tỷ giá hối đoái mới nhất của wOETH được lấy từ địa chỉ nhà cung cấp tỷ giá. Nếu tỷ giá đã thay đổi, tỷ giá hối đoái mới này được sử dụng để cập nhật tổng tích số dư ảo (vb_prod), số dư ảo tương ứng với tài sản và tổng số dư ảo. Sau đó, hàm `_update_supply` được gọi để cập nhật tổng cung LP. Điều này giải thích tại sao thanh khoản không được thêm vào wOETH trong vòng lặp xóa/thêm thanh khoản trước đó. Nếu lãi suất thay đổi, hàm `_update_rates` sẽ được gọi để cập nhật lãi suất khi thanh khoản được bổ sung. Chỉ trong bước này, cần sử dụng chênh lệch lãi suất để khôi phục `vb_prod` và nguồn cung đã được xử lý. Hàm `_calc_supply` vẫn sẽ được gọi để tính toán nguồn cung mới và tổng tích số dư ảo. Vì số dư ảo đã được khôi phục về giá trị khác không thông qua các thao tác trên, nguồn cung cuối cùng được tính toán sẽ nhỏ hơn nguồn cung trước đó, và phần yETH này sẽ được đốt cho hợp đồng.

Tại thời điểm này, các giá trị của tổng tích số dư ảo (vb_prod), tổng số dư ảo (vb_sum) và tổng nguồn cung đã được cập nhật thành các giá trị sau: vb_prod ≈ 4,34e19, vb_sum ≈ 1,0926e22, supply ≈ 9,98e21. ... 5. Sau đó, kẻ tấn công đã đổi tất cả các mã thông báo LP được đúc trong hai lần bơm thanh khoản trước đó bằng hàm `remove_liquidity`. Vì phần LP này được đúc trong bước thứ ba của việc khuếch đại và thao túng nguồn cung, và việc đổi thưởng diễn ra trong bước thứ tư của việc khôi phục và giảm nguồn cung, nên kẻ tấn công có thể đổi nhiều mã thông báo hơn dự kiến, do đó làm giảm số dư tài sản và nguồn cung trong nhóm. Kẻ tấn công đã sử dụng cùng một phương pháp để thực hiện một chu kỳ thao túng một lần nữa, dần dần giảm nguồn cung LP và số dư ảo. Lưu ý lệnh gọi đến hàm `rebase` của hợp đồng `OETHVaultCore`. Mục đích của nó là cập nhật tỷ giá hối đoái của `wOETH`, cho phép hàm `update_rates` truy xuất tỷ giá mới và khôi phục các giá trị của tích số dư ảo và nguồn cung. Sau thao tác cuối cùng, kẻ tấn công có thể trực tiếp làm trống tất cả tài sản trong nhóm sau khi loại bỏ thanh khoản, làm cho tích số dư ảo (vb_prod), tổng số dư ảo (vb_sum) và tổng nguồn cung đều bằng 0. 6. Khi nhóm đã hoàn toàn trống rỗng, kẻ tấn công bắt đầu thêm thanh khoản bụi vào nhóm trống: Vì lãi suất của 8 tài sản LSD cơ bản gần bằng... Trong 1e18, sau khi thêm số lượng mã thông báo bụi, số dư ảo của mỗi tài sản sẽ bằng trực tiếp với số lượng mã thông báo. Khi nguồn cung bằng 0, việc thêm thanh khoản sẽ gọi hàm `_calc_vb_prod_sum` nội bộ để tính toán lại tổng tích số dư ảo (vb_prod), tổng số dư ảo và tổng nguồn cung hiện tại: Các giá trị này sau đó được chuyển đến hàm `_calc_supply` để tính toán nguồn cung mới, tức là số lượng LP được đúc cho kẻ tấn công. Trong một lần lặp của hàm `_calc_supply`, giá trị `supply` cũng bị tràn do `unsafe_mul`, dẫn đến giá trị `supply` được tính toán theo cấp độ lớn (2.354e56), từ đó đúc một số lượng mã thông báo yETH tương ứng cho kẻ tấn công. Cuối cùng, kẻ tấn công sử dụng AMM để trực tiếp bán và trao đổi yETH, sau đó hoàn trả khoản vay flash và lợi nhuận. Theo phân tích của MistTrack, một công cụ theo dõi trên chuỗi và chống rửa tiền, kẻ tấn công đã kiếm được khoảng 9 triệu đô la trong sự cố này, với số tiền ban đầu đến từ một lượng nhỏ ETH được chuyển từ Railgun. Sau khi khởi động cuộc tấn công, kẻ tấn công đầu tiên đã chuyển 1.100 ETH sang Tornado Cash, trong đó 100 ETH đã được rút để sử dụng tiếp: [Hình ảnh Tornado Cash] 6 triệu đô la lợi nhuận (bao gồm 128 ETH, 48,96 cbETH, 203,55 rETH, 742,63 frx, 857,48 px và 167,67 st) sau đó được chuyển đến địa chỉ 0xa80d3f2022f6bfd0b260bf16d72cad025440c822. Điều đáng chú ý là Yearn sau đó đã thu hồi được 2,4 triệu đô la bằng cách phá hủy số pxETH do tin tặc nắm giữ. Số pxETH này đã được đúc lại và trả lại cho Redacted Cartel. Ví đa chữ ký.

(https://etherscan.io/tx/0x0e83bb95bb9d05fb81213b2fad11c01ea671796752e8770b09935f7052691c35)
MistTrack đã đánh dấucác địa chỉ liên quanvà sẽ tiếp tục theo dõi các chuyển động của quỹ.
Tóm tắtTóm tắt
Cốt lõi của cuộc tấn công này nằm ở việc kẻ tấn công lợi dụng một phép toán không an toàn trong logic triển khai hợp đồng Nhóm hoán đổi ổn định có trọng số yETH của giao thức Yearn để tính toán nguồn cung LP khi thêm thanh khoản, dẫn đến lỗi tràn và lỗi làm tròn. Bằng cách xây dựng cẩn thận các giá trị số dư và nguồn cung ảo cụ thể, kẻ tấn công đã khuếch đại lỗi do lỗi này gây ra, cho phép chúng đúc một lượng lớn token LP để kiếm lời. Đội ngũ bảo mật SlowMist khuyến nghị các nhóm dự án và kiểm toán viên tăng cường kiểm tra phạm vi bảo vệ cho các tình huống cực đoan và điều kiện biên khi gặp phải những tình huống tương tự. Họ cũng nên sử dụng các phép toán an toàn và thực hiện kiểm tra khi tính toán các biến cốt lõi để ngăn chặn các lỗ hổng nghiêm trọng như tràn ảnh hưởng đến bảo mật giao thức.