Các cuộc tấn công tái nhập đại diện cho một trong những lỗ hổng nổi tiếng nhất trong bảo mật hợp đồng thông minh, chịu trách nhiệm cho hàng triệu đô la bị đánh cắp qua nhiều giao thức blockchain khác nhau. Bài viết này khám phá cơ chế đằng sau các lỗ hổng tái nhập và trình bày các kỹ thuật phòng ngừa toàn diện mà các nhà phát triển chú trọng đến bảo mật nên thực hiện.
Tấn công Tái nhập là gì?
Cốt lõi của một cuộc tấn công tái nhập xảy ra khi một hàm trong một hợp đồng thông minh được gọi nhiều lần trước khi hoàn thành thực thi trước đó. Lỗ hổng cơ bản phát sinh khi một hợp đồng thông minh gọi một hợp đồng bên ngoài trước khi giải quyết các thay đổi trạng thái của chính nó, tạo ra cơ hội để khai thác.
Trong một kịch bản điển hình, Hợp đồng A tương tác với Hợp đồng B bằng cách gọi một trong những chức năng của nó. Lỗ hổng bảo mật nghiêm trọng xuất hiện khi Hợp đồng B có khả năng gọi lại vào Hợp đồng A trong khi Hợp đồng A vẫn đang thực thi chức năng ban đầu của nó. Mô hình tương tác đệ quy này tạo ra nền tảng cho một lỗ hổng tiềm ẩn.
Cách thức các cuộc tấn công Reentrancy hoạt động: Phân tích từng bước
Xem xét kịch bản này: Hợp đồng A giữ tổng cộng 10 ETH, trong đó Hợp đồng B đã gửi 1 ETH vào Hợp đồng A. Lỗ hổng trở nên có thể khai thác được khi Hợp đồng B cố gắng rút tiền của mình thông qua chuỗi sau:
Hợp đồng B gọi hàm withdraw() trong Hợp đồng A
Hợp đồng A xác minh rằng số dư của Hợp đồng B lớn hơn 0 (đã vượt qua kiểm tra)
Hợp đồng A gửi ETH đến Hợp đồng B, kích hoạt chức năng fallback của Hợp đồng B
Trước khi Hợp đồng A có thể cập nhật số dư của Hợp đồng B thành zero, hàm fallback trong Hợp đồng B gọi withdraw() một lần nữa
Hợp đồng A kiểm tra số dư của Hợp đồng B, vẫn hiển thị 1 ETH (chưa được cập nhật)
Quá trình lặp lại cho đến khi quỹ của Hợp đồng A bị cạn kiệt
Lỗ hổng chính là cập nhật số dư diễn ra sau khi chuyển ETH, cho phép kẻ tấn công khai thác cùng một số dư nhiều lần trước khi nó được thiết lập về không.
Giải phẫu của một cuộc tấn công: Triển khai kỹ thuật
Hãy xem xét mẫu mã dễ bị tổn thương:
solidity
// Hợp đồng EtherStore dễ bị tổn thương
hợp đồng EtherStore {
mapping(address => uint) công khai balances;
function deposit() public payable {
balances[msg.sender] += msg.value;
}
chức năng rút tất cả() công cộng {
uint bal = balances[msg.sender];
require(bal > 0);
// Lỗ hổng: Gọi bên ngoài trước khi cập nhật trạng thái
(bool sent, ) = msg.sender.call{value: bal}("");
require(sent, "Không thể gửi Ether");
// Cập nhật trạng thái xảy ra quá muộn
balances[msg.sender] = 0;
}
}
Bây giờ, hãy xem cách một kẻ tấn công có thể khai thác lỗ hổng này:
solidity
// Hợp đồng tấn công khai thác lỗ hổng gọi lại
hợp đồng Tấn công {
EtherStore công khai etherStore;
constructor(địa chỉ _etherStoreAddress) {
etherStore = EtherStore(_etherStoreAddress);
}
// Hàm dự phòng được gọi khi EtherStore gửi Ether
nhận() thanh toán bên ngoài {
nếu(địa chỉ(etherStore).số dư >= 1 ether) {
// Nhập lại hàm withdrawAll
etherStore.withdrawAll();
}
}
function attack() external payable {
require(msg.value >= 1 ether);
// Nạp tiền để thiết lập số dư trong EtherStore
etherStore.deposit{value: 1 ether}();
// Bắt đầu quá trình rút tiền, kích hoạt cuộc tấn công tái nhập
etherStore.withdrawAll();
}
}
Chuỗi tấn công bắt đầu khi kẻ tấn công gọi attack(). Điều này gửi 1 ETH để thiết lập một số dư và sau đó gọi withdrawAll(). Khi EtherStore gửi ETH trở lại, nó kích hoạt hàm receive() trong hợp đồng Attack, hàm này lại gọi withdrawAll() trước khi số dư được cập nhật. Vòng lặp này tiếp tục cho đến khi EtherStore bị rút hết tiền.
Kỹ Thuật Phòng Ngừa Toàn Diện
Các nhà phát triển chú trọng đến bảo mật có thể triển khai ba kỹ thuật mạnh mẽ để bảo vệ chống lại các lỗ hổng tái nhập:
1. Bảo vệ cấp độ chức năng: Bộ điều chỉnh nonReentrant
Bảo vệ phổ biến nhất ở cấp độ chức năng cá nhân là triển khai một bộ bảo vệ tái nhập:
solidity
hợp đồng ReentrancyGuard {
bool private locked = false;
modifier nonReentrant() {
require(!locked, "Reentrant call");
locked = true;
_;
locked = false;
}
// Áp dụng bộ sửa đổi này cho các hàm dễ bị tổn thương
chức năng rútTiền() công khai khôngReEntrant {
// Mã chức năng được bảo vệ khỏi việc gọi lại
}
}
Cách tiếp cận này khóa hợp đồng một cách hiệu quả trong quá trình thực thi hàm, ngăn chặn bất kỳ cuộc gọi đệ quy nào cho đến khi hàm hoàn tất và mở khóa trạng thái.
2. Bảo vệ Chéo Chức Năng: Mô hình Kiểm tra-Hiệu ứng-Interactions
Mô hình bảo mật cơ bản này tái cấu trúc mã để loại bỏ các lỗ hổng trên nhiều chức năng:
solidity
// TRIỂN KHAI DỄ BỊ TẤN CÔNG
function withdrawAll() public {
uint bal = balances[msg.sender];
require(bal > 0);
(bool sent, ) = msg.sender.call{value: bal}("");
require(sent, "Không thể gửi Ether");
balances[msg.sender] = 0; // Cập nhật trạng thái sau khi tương tác
}
// TRIỂN KHAI BẢO MẬT
hàm rútTấtCả() công khai {
uint bal = balances[msg.sender];
require(bal > 0); // Kiểm tra
balances[msg.sender] = 0; // Hiệu ứng (thay đổi trạng thái)
(bool sent, ) = msg.sender.call{value: bal}(""); // Tương tác
require(sent, "Gửi Ether không thành công");
}
Bằng cách tuân theo mẫu Checks-Effects-Interactions, hợp đồng cập nhật trạng thái của nó trước mọi tương tác bên ngoài, đảm bảo rằng ngay cả khi một kẻ tấn công cố gắng truy cập lại, họ sẽ gặp phải trạng thái đã được cập nhật (số dư bằng không).
3. Bảo vệ Cấp Dự án: Bộ bảo vệ tái nhập toàn cầu
Đối với các dự án phức tạp với nhiều hợp đồng tương tác, việc triển khai một bộ bảo vệ tái nhập toàn cầu cung cấp sự bảo vệ trên toàn hệ thống:
solidity
hợp đồng GlobalReentrancyGuard {
bool private _notEntered = true;
// Tất cả các hợp đồng trong dự án đều kế thừa từ GlobalReentrancyGuard
hợp đồng SecureContract là GlobalReentrancyGuard {
hàm vulnerableOperation() công khai globalNonReentrant {
// Được bảo vệ khỏi việc gọi lại trong toàn bộ dự án
}
}
Cách tiếp cận này đặc biệt có giá trị trong việc bảo vệ chống lại các cuộc tấn công tái nhập chéo hợp đồng trong các giao thức DeFi, nơi nhiều hợp đồng tương tác với nhau.
Tác động thực tế của các lỗ hổng tái nhập
Lỗ hổng tái nhập đã dẫn đến một số vụ khai thác tàn phá nhất trong lịch sử blockchain. Vụ hack DAO nổi tiếng vào năm 2016 đã dẫn đến việc đánh cắp khoảng 3.6 triệu ETH ( trị giá khoảng $50 triệu vào thời điểm đó ) và cuối cùng đã dẫn đến việc phân nhánh Ethereum cứng tạo ra Ethereum Classic.
Gần đây, vào năm 2020, giao thức Lendf.Me đã mất khoảng $25 triệu do một cuộc tấn công tái nhập, cho thấy rằng mặc dù nhận thức đã tăng lên, những lỗ hổng này vẫn tiếp tục gây ra những rủi ro đáng kể cho bảo mật hợp đồng thông minh.
Thực hành bảo mật tốt nhất
Ngoài các kỹ thuật cụ thể đã đề cập, các nhà phát triển nên tuân theo những thực tiễn bảo mật bổ sung sau đây:
Luôn sử dụng phiên bản Solidity mới nhất cung cấp các tính năng bảo mật được cải thiện
Các hợp đồng chủ đề phải trải qua các cuộc kiểm toán bảo mật kỹ lưỡng bởi các công ty uy tín
Triển khai phạm vi kiểm tra toàn diện bao gồm các bài kiểm tra đơn vị cho các trường hợp đặc biệt
Bắt đầu với mức độ tiếp xúc ETH tối thiểu khi triển khai các hợp đồng mới vào sản xuất
Xem xét xác minh chính thức cho các hợp đồng có giá trị cao
Bằng cách thực hiện các kỹ thuật phòng thủ này và tuân theo các thực hành bảo mật tốt nhất, các nhà phát triển có thể giảm đáng kể nguy cơ của các cuộc tấn công tái nhập và xây dựng các hợp đồng thông minh an toàn hơn cho các ứng dụng blockchain.
Trang này có thể chứa nội dung của bên thứ ba, được cung cấp chỉ nhằm mục đích thông tin (không phải là tuyên bố/bảo đảm) và không được coi là sự chứng thực cho quan điểm của Gate hoặc là lời khuyên về tài chính hoặc chuyên môn. Xem Tuyên bố từ chối trách nhiệm để biết chi tiết.
Các cuộc tấn công tái nhập trong Hợp đồng thông minh: Hiểu về lỗ hổng và triển khai các chiến lược ngăn chặn
Các cuộc tấn công tái nhập đại diện cho một trong những lỗ hổng nổi tiếng nhất trong bảo mật hợp đồng thông minh, chịu trách nhiệm cho hàng triệu đô la bị đánh cắp qua nhiều giao thức blockchain khác nhau. Bài viết này khám phá cơ chế đằng sau các lỗ hổng tái nhập và trình bày các kỹ thuật phòng ngừa toàn diện mà các nhà phát triển chú trọng đến bảo mật nên thực hiện.
Tấn công Tái nhập là gì?
Cốt lõi của một cuộc tấn công tái nhập xảy ra khi một hàm trong một hợp đồng thông minh được gọi nhiều lần trước khi hoàn thành thực thi trước đó. Lỗ hổng cơ bản phát sinh khi một hợp đồng thông minh gọi một hợp đồng bên ngoài trước khi giải quyết các thay đổi trạng thái của chính nó, tạo ra cơ hội để khai thác.
Trong một kịch bản điển hình, Hợp đồng A tương tác với Hợp đồng B bằng cách gọi một trong những chức năng của nó. Lỗ hổng bảo mật nghiêm trọng xuất hiện khi Hợp đồng B có khả năng gọi lại vào Hợp đồng A trong khi Hợp đồng A vẫn đang thực thi chức năng ban đầu của nó. Mô hình tương tác đệ quy này tạo ra nền tảng cho một lỗ hổng tiềm ẩn.
Cách thức các cuộc tấn công Reentrancy hoạt động: Phân tích từng bước
Xem xét kịch bản này: Hợp đồng A giữ tổng cộng 10 ETH, trong đó Hợp đồng B đã gửi 1 ETH vào Hợp đồng A. Lỗ hổng trở nên có thể khai thác được khi Hợp đồng B cố gắng rút tiền của mình thông qua chuỗi sau:
Lỗ hổng chính là cập nhật số dư diễn ra sau khi chuyển ETH, cho phép kẻ tấn công khai thác cùng một số dư nhiều lần trước khi nó được thiết lập về không.
Giải phẫu của một cuộc tấn công: Triển khai kỹ thuật
Hãy xem xét mẫu mã dễ bị tổn thương:
solidity // Hợp đồng EtherStore dễ bị tổn thương hợp đồng EtherStore { mapping(address => uint) công khai balances;
}
Bây giờ, hãy xem cách một kẻ tấn công có thể khai thác lỗ hổng này:
solidity // Hợp đồng tấn công khai thác lỗ hổng gọi lại hợp đồng Tấn công { EtherStore công khai etherStore;
}
Chuỗi tấn công bắt đầu khi kẻ tấn công gọi attack(). Điều này gửi 1 ETH để thiết lập một số dư và sau đó gọi withdrawAll(). Khi EtherStore gửi ETH trở lại, nó kích hoạt hàm receive() trong hợp đồng Attack, hàm này lại gọi withdrawAll() trước khi số dư được cập nhật. Vòng lặp này tiếp tục cho đến khi EtherStore bị rút hết tiền.
Kỹ Thuật Phòng Ngừa Toàn Diện
Các nhà phát triển chú trọng đến bảo mật có thể triển khai ba kỹ thuật mạnh mẽ để bảo vệ chống lại các lỗ hổng tái nhập:
1. Bảo vệ cấp độ chức năng: Bộ điều chỉnh nonReentrant
Bảo vệ phổ biến nhất ở cấp độ chức năng cá nhân là triển khai một bộ bảo vệ tái nhập:
solidity hợp đồng ReentrancyGuard { bool private locked = false;
}
Cách tiếp cận này khóa hợp đồng một cách hiệu quả trong quá trình thực thi hàm, ngăn chặn bất kỳ cuộc gọi đệ quy nào cho đến khi hàm hoàn tất và mở khóa trạng thái.
2. Bảo vệ Chéo Chức Năng: Mô hình Kiểm tra-Hiệu ứng-Interactions
Mô hình bảo mật cơ bản này tái cấu trúc mã để loại bỏ các lỗ hổng trên nhiều chức năng:
solidity // TRIỂN KHAI DỄ BỊ TẤN CÔNG function withdrawAll() public { uint bal = balances[msg.sender]; require(bal > 0);
}
// TRIỂN KHAI BẢO MẬT hàm rútTấtCả() công khai { uint bal = balances[msg.sender]; require(bal > 0); // Kiểm tra
}
Bằng cách tuân theo mẫu Checks-Effects-Interactions, hợp đồng cập nhật trạng thái của nó trước mọi tương tác bên ngoài, đảm bảo rằng ngay cả khi một kẻ tấn công cố gắng truy cập lại, họ sẽ gặp phải trạng thái đã được cập nhật (số dư bằng không).
3. Bảo vệ Cấp Dự án: Bộ bảo vệ tái nhập toàn cầu
Đối với các dự án phức tạp với nhiều hợp đồng tương tác, việc triển khai một bộ bảo vệ tái nhập toàn cầu cung cấp sự bảo vệ trên toàn hệ thống:
solidity hợp đồng GlobalReentrancyGuard { bool private _notEntered = true;
}
// Tất cả các hợp đồng trong dự án đều kế thừa từ GlobalReentrancyGuard hợp đồng SecureContract là GlobalReentrancyGuard { hàm vulnerableOperation() công khai globalNonReentrant { // Được bảo vệ khỏi việc gọi lại trong toàn bộ dự án } }
Cách tiếp cận này đặc biệt có giá trị trong việc bảo vệ chống lại các cuộc tấn công tái nhập chéo hợp đồng trong các giao thức DeFi, nơi nhiều hợp đồng tương tác với nhau.
Tác động thực tế của các lỗ hổng tái nhập
Lỗ hổng tái nhập đã dẫn đến một số vụ khai thác tàn phá nhất trong lịch sử blockchain. Vụ hack DAO nổi tiếng vào năm 2016 đã dẫn đến việc đánh cắp khoảng 3.6 triệu ETH ( trị giá khoảng $50 triệu vào thời điểm đó ) và cuối cùng đã dẫn đến việc phân nhánh Ethereum cứng tạo ra Ethereum Classic.
Gần đây, vào năm 2020, giao thức Lendf.Me đã mất khoảng $25 triệu do một cuộc tấn công tái nhập, cho thấy rằng mặc dù nhận thức đã tăng lên, những lỗ hổng này vẫn tiếp tục gây ra những rủi ro đáng kể cho bảo mật hợp đồng thông minh.
Thực hành bảo mật tốt nhất
Ngoài các kỹ thuật cụ thể đã đề cập, các nhà phát triển nên tuân theo những thực tiễn bảo mật bổ sung sau đây:
Bằng cách thực hiện các kỹ thuật phòng thủ này và tuân theo các thực hành bảo mật tốt nhất, các nhà phát triển có thể giảm đáng kể nguy cơ của các cuộc tấn công tái nhập và xây dựng các hợp đồng thông minh an toàn hơn cho các ứng dụng blockchain.