[Hóng hớt Hacking#4] Address Spoofing Attack - The Combination of ERC2771 and Multicall is a Bomb
THIRDWEB một nền tảng “mì ăn liền” cho developer, giúp triển khai NFT, Marketplace, token ERC20,… chỉ bằng một cú click. Tiện thì tiện thật, nhưng tiện quá đôi khi lại… hại điện.
Trong các smart contract được triển khai bởi Thirdweb, chúng có sự phụ thuộc vào hai thư viện ERC2771 và Multicall từ OpenZeppelin. Sự tương tác không an toàn giữa hai thành phần này đã tạo ra một lỗ hổng bảo mật nghiêm trọng, gây ảnh hưởng trên quy mô lớn trong cộng đồng blockchain. Hơn 8,000 smart contract trên nhiều chuỗi khác nhau đã được triển khai với mã nguồn chứa lỗ hổng, dẫn đến thiệt hại ước tính gần một triệu đô la.
Để hiểu rõ vấn đề, chúng ta cần xem xét cơ chế hoạt động của hai thành phần cốt lõi.
ERC2771 là gì?
ERC-2771 là một chuẩn cho "meta transaction". Nôm na là thế này: thay vì bạn (người dùng) tự trả gas để gọi một hàm trong contract, bạn sẽ ký một "ủy nhiệm" rồi đưa cho một "shipper" (Forwarder) tin cậy. Anh shipper này sẽ thay bạn gửi giao dịch đi và trả phí gas.
Vấn đề là, khi shipper gọi contract, msg.sender sẽ là địa chỉ của shipper chứ không phải của bạn. Để contract biết "ai mới là chủ thật sự", anh shipper phải "nhét" địa chỉ của bạn vào 20 bytes cuối cùng của dữ liệu giao dịch (calldata). Contract sau đó sẽ dùng hàm _msgSender() để "moi" 20 bytes cuối này ra và xác định đúng người gửi.
Multicall
Multicall là một tiện ích cực hay của OpenZeppelin. Nó cho phép bạn gom nhiều lệnh gọi hàm khác nhau vào chung một giao dịch duy nhất. Thay vì phải đi chợ 5 lần để mua 5 món, bạn chỉ cần đi 1 lần và mua hết luôn. Vừa nhanh, vừa tiết kiệm gas. Quá tuyệt!
Vulnerability Analysis
Bản thân các tính năng này không gây ra vấn đề khi được sử dụng độc lập. Lỗ hổng chỉ xuất hiện khi chúng được kết hợp với nhau. Do đó, các contracts bị ảnh hưởng đã kế thừa lỗ hổng từ sự kết hợp của hai tính năng này.
Root cause
Thông thường, người dùng gọi đến contract Forwarder (được contract token tin cậy) để thực thi các hàm như transfer, transferFrom, burn... Trong mỗi lệnh gọi, contract token sẽ xác định người gửi thật bằng cách tách 20 bytes cuối cùng của calldata do Forwarder gửi đến.
Tuy nhiên, logic xác định này đã bị phá vỡ bởi hàm multicall.
Đây là đoạn mã của Forwarder:
Kẻ tấn công đã tạo ra req.data với cấu trúc như sau:
Hãy tập trung vào req.data (ví dụ từ dữ liệu của kẻ tấn công):
Chúng ta có thể thấy độ dài của mảng arrBytes được xác định trong các byte đầu tiên của calldata (0x01). Contract sẽ nối req.from vào cuối req.data trước khi Forwarder thực hiện lệnh gọi bên ngoài. Tuy nhiên, việc nối thêm này không có ý nghĩa đối với hàm multicall, vì multicall chỉ xử lý dữ liệu bên trong mảng bytes đã được khai báo với độ dài nghiêm ngặt.
- Kẻ tấn công tạo một
calldatalồng nhau. Lệnh gọi bên ngoài làmulticall, và bên trong là lệnh gọi đến hàm mục tiêu (ví dụ:transferFrom). Địa chỉ của kẻ tấn công được đưa vào làm tham số cho hàmtransferFrom. - Contract Forwarder nhận
calldatanày và, theo logic củaERC2771, nó nối thêm địa chỉ của người gửi hợp lệ (chính là Forwarder) vào cuối cùng của toàn bộcalldata. - Khi lệnh gọi được thực thi, hàm
multicallbỏ qua dữ liệu được nối thêm ở cuối vì nó nằm ngoài độ dài đã khai báo. - Bên trong
multicall, hàmtransferFromđược gọi. Tại thời điểm này, logic_msgSender()củaERC2771được kích hoạt. Nó đọc 20 bytes cuối củacalldatamà nó nhận được, và trớ trêu thay, đây lại chính là địa chỉ của kẻ tấn công được định sẵn trong bước 1. - Contract xác thực sai kẻ tấn công là người gửi và thực hiện giao dịch, dẫn đến mất mát tài sản.
Migration
Ngay sau khi phát hiện, OpenZeppelin đã tung ra bản vá cho cả phiên bản 4.x và 5.x, cho phép Multicall và ERC2771Context "chung sống hòa bình".
Hướng dẫn migration chính thức do Thirdweb công bố có tại đây: Link.
Conclusion
Vụ việc này như một hồi chuông cảnh tỉnh cho cả cộng đồng Web3. Nó cho thấy việc bảo mật chủ động quan trọng đến mức nào, đặc biệt là trong một thế giới phát triển nhanh như vũ bão này.
Một tính năng hay, một tiện ích tốt, nhưng khi kết hợp lại với nhau có thể tạo ra những lỗ hổng không ai ngờ tới.
Túm lại một câu: làm gì thì làm, bảo mật phải luôn đặt lên hàng đầu. Các nền tảng như Thirdweb và các tổ chức blockchain nên coi việc audit bảo mật cho toàn bộ quy trình phát triển smart contract là một điều bắt buộc, và tốt nhất là nhờ các công ty audit uy tín bên thứ ba “soi” giúp.