Contents
Trong ngành công nghiệp blockchain, một số vụ tấn công lớn nhất đã dẫn đến việc đánh cắp hàng triệu đô la tiền điện tử là do tấn công reentrancy. Mặc dù các vụ tấn công này đã trở nên ít phổ biến hơn trong những năm gần đây, chúng vẫn là mối đe dọa đáng kể đối với các ứng dụng blockchain và người dùng. Bài viết này sẽ giải thích chi tiết về tấn công reentrancy, cách chúng được thực hiện và các biện pháp phòng ngừa mà các nhà phát triển có thể áp dụng.
Tấn Công Reentrancy Là Gì?
Tấn công reentrancy xảy ra khi một hàm hợp đồng thông minh dễ bị tổn thương thực hiện cuộc gọi ngoài đến một hợp đồng độc hại, tạm thời nhường quyền kiểm soát luồng giao dịch. Hợp đồng độc hại sau đó liên tục gọi lại hàm hợp đồng thông minh gốc trước khi nó hoàn thành việc thực thi, đồng thời rút cạn các quỹ của nó.
Cụ thể, một giao dịch rút tiền trên blockchain Ethereum tuân theo chu kỳ ba bước: xác nhận số dư, chuyển tiền và cập nhật số dư. Nếu một tội phạm mạng có thể chiếm quyền kiểm soát chu kỳ này trước khi cập nhật số dư, họ có thể liên tục rút tiền cho đến khi ví bị rút cạn.
Minh họa về Tấn Công Reentrancy của DAO
Một trong những vụ tấn công blockchain nổi tiếng nhất, vụ hack Ethereum DAO, như được đưa tin bởi Coindesk, là một tấn công reentrancy đã dẫn đến việc mất hơn 60 triệu đô la ETH và thay đổi cơ bản quá trình phát triển của đồng tiền điện tử lớn thứ hai.
Tấn Công Reentrancy Hoạt Động Như Thế Nào?
Hãy tưởng tượng một ngân hàng ở quê hương bạn, nơi người dân địa phương giữ tiền của họ; tổng thanh khoản của ngân hàng là 1 triệu đô la. Tuy nhiên, hệ thống kế toán của ngân hàng có lỗi – nhân viên chờ đến tối để cập nhật số dư ngân hàng.
Bạn bè của bạn là nhà đầu tư đến thăm thị trấn và phát hiện ra lỗi kế toán. Anh ta tạo một tài khoản và gửi 100.000 đô la. Một ngày sau, anh ta rút 100.000 đô la. Sau một giờ, anh ta thử rút thêm 100.000 đô la nữa. Vì ngân hàng chưa cập nhật số dư của anh ta, nó vẫn hiển thị 100.000 đô la. Vì vậy, anh ta nhận được tiền. Anh ta làm điều này lặp đi lặp lại cho đến khi không còn tiền. Nhân viên chỉ nhận ra không còn tiền khi họ cân đối sổ sách vào buổi tối.
Trong bối cảnh của một hợp đồng thông minh, quá trình diễn ra như sau:
- Một tội phạm mạng xác định một hợp đồng thông minh “X” có lỗ hổng.
- Kẻ tấn công khởi tạo một giao dịch hợp pháp đến hợp đồng mục tiêu X để gửi tiền đến một hợp đồng độc hại “Y.” Trong quá trình thực thi, Y gọi hàm dễ bị tổn thương trong X.
- Quá trình thực thi của hợp đồng X bị tạm dừng hoặc trì hoãn khi hợp đồng chờ tương tác với sự kiện bên ngoài.
- Trong khi thực thi bị tạm dừng, kẻ tấn công liên tục gọi lại cùng một hàm dễ bị tổn thương trong X, kích hoạt lại việc thực thi nhiều lần nhất có thể.
- Với mỗi lần gọi lại, trạng thái của hợp đồng bị thao túng, cho phép kẻ tấn công rút tiền từ X đến Y.
- Khi các quỹ đã bị rút cạn, việc gọi lại dừng lại, quá trình thực thi bị trì hoãn của X cuối cùng hoàn thành, và trạng thái của hợp đồng được cập nhật dựa trên lần gọi lại cuối cùng.
Nói chung, kẻ tấn công thành công khai thác lỗ hổng reentrancy để thu lợi, đánh cắp tiền từ hợp đồng.
Ví Dụ Về Tấn Công Reentrancy
Vậy tấn công reentrancy có thể xảy ra như thế nào khi được triển khai? Dưới đây là một hợp đồng thông minh giả định với cổng reentrancy. Chúng ta sẽ sử dụng tên gọi có tính chất minh họa để dễ theo dõi.
// Hợp đồng dễ bị tổn thương với lỗ hổng reentrancy
pragma solidity ^0.8.0;
contract VulnerableContract {
mapping(address => uint256) private balances;
function deposit() public payable {
balances[msg.sender] += msg.value;
}
function withdraw(uint256 amount) public {
require(balances[msg.sender] >= amount, "Insufficient balance");
(bool success, ) = msg.sender.call{value: amount}("");
require(success, "Transfer failed");
balances[msg.sender] -= amount;
}
}
Hợp đồng VulnerableContract cho phép người dùng gửi ETH vào hợp đồng bằng hàm deposit. Người dùng sau đó có thể rút ETH đã gửi bằng hàm withdraw. Tuy nhiên, có một lỗ hổng reentrancy trong hàm withdraw. Khi một người dùng rút tiền, hợp đồng chuyển số tiền yêu cầu đến địa chỉ của người dùng trước khi cập nhật số dư, tạo cơ hội cho kẻ tấn công khai thác.
Dưới đây là cách hợp đồng của kẻ tấn công sẽ trông như thế nào.
// Hợp đồng của kẻ tấn công để khai thác lỗ hổng reentrancy
pragma solidity ^0.8.0;
interface VulnerableContractInterface {
function withdraw(uint256 amount) external;
}
contract AttackerContract {
VulnerableContractInterface private vulnerableContract;
address private targetAddress;
constructor(address _vulnerableContractAddress) {
vulnerableContract = VulnerableContractInterface(_vulnerableContractAddress);
targetAddress = msg.sender;
}
// Hàm để kích hoạt tấn công
function attack() public payable {
// Gửi một số ETH vào hợp đồng dễ bị tổn thương
vulnerableContract.deposit{value: msg.value}();
// Gọi hàm withdraw của hợp đồng dễ bị tổn thương
vulnerableContract.withdraw(msg.value);
}
// Hàm nhận để nhận tiền từ hợp đồng dễ bị tổn thương
receive() external payable {
if (address(vulnerableContract).balance >= 1 ether) {
// Gọi lại hàm withdraw của hợp đồng dễ bị tổn thương
vulnerableContract.withdraw(1 ether);
}
}
// Hàm để rút tiền đã đánh cắp từ hợp đồng dễ bị tổn thương
function withdrawStolenFunds() public {
require(msg.sender == targetAddress, "Unauthorized");
(bool success, ) = targetAddress.call{value: address(this).balance}("");
require(success, "Transfer failed");
}
}
Khi tấn công được khởi động:
- AttackerContract nhận địa chỉ của VulnerableContract trong hàm khởi tạo và lưu trữ nó trong biến vulnerableContract.
- Hàm attack được gọi bởi kẻ tấn công, gửi một số ETH vào VulnerableContract bằng hàm deposit và sau đó ngay lập tức gọi hàm withdraw của VulnerableContract.
- Hàm withdraw trong VulnerableContract chuyển số tiền yêu cầu ETH đến AttackerContract của kẻ tấn công trước khi cập nhật số dư, nhưng vì hợp đồng của kẻ tấn công bị tạm dừng trong cuộc gọi bên ngoài, hàm chưa hoàn thành.
- Hàm receive trong AttackerContract được kích hoạt vì VulnerableContract đã gửi ETH đến hợp đồng này trong cuộc gọi bên ngoài.
- Hàm receive kiểm tra nếu số dư của AttackerContract ít nhất là 1 ether (số tiền để rút), sau đó nó gọi lại hàm withdraw của VulnerableContract.
- Các bước từ ba đến năm lặp lại cho đến khi VulnerableContract hết tiền và hợp đồng của kẻ tấn công tích lũy được một lượng lớn ETH.
- Cuối cùng, kẻ tấn công có thể gọi hàm withdrawStolenFunds trong AttackerContract để đánh cắp tất cả các quỹ đã tích lũy trong hợp đồng của họ.
Tấn công có thể xảy ra rất nhanh, tùy thuộc vào hiệu suất của mạng. Khi liên quan đến các hợp đồng thông minh phức tạp như vụ hack DAO, dẫn đến việc tách Ethereum thành Ethereum và Ethereum Classic, tấn công xảy ra trong vài giờ.
Cách Phòng Ngừa Tấn Công Reentrancy
Để ngăn chặn tấn công reentrancy, chúng ta cần sửa đổi hợp đồng thông minh dễ bị tổn thương để tuân theo các thực hành tốt nhất cho việc phát triển hợp đồng thông minh an toàn. Trong trường hợp này, chúng ta nên triển khai mẫu “kiểm tra-tác động-tương tác” như trong đoạn mã dưới đây.
// Hợp đồng an toàn với mẫu "kiểm tra-tác động-tương tác"
pragma solidity ^0.8.0;
contract SecureContract {
mapping(address => uint256) private balances;
mapping(address => bool) private isLocked;
function deposit() public payable {
balances[msg.sender] += msg.value;
}
function withdraw(uint256 amount) public {
require(balances[msg.sender] >= amount, "Insufficient balance");
require(!isLocked[msg.sender], "Withdrawal in progress");
// Khóa tài khoản của người gửi để ngăn chặn reentrancy
isLocked[msg.sender] = true;
// Thực hiện thay đổi trạng thái
balances[msg.sender] -= amount;
// Tương tác với hợp đồng bên ngoài sau khi thay đổi trạng thái
(bool success, ) = msg.sender.call{value: amount}("");
require(success, "Transfer failed");
// Mở khóa tài khoản của người gửi
isLocked[msg.sender] = false;
}
}
Trong phiên bản đã sửa này, chúng ta đã giới thiệu một bản đồ isLocked để theo dõi liệu một tài khoản cụ thể có đang trong quá trình rút tiền hay không. Khi một người dùng khởi tạo việc rút tiền, hợp đồng kiểm tra xem tài khoản của họ có bị khóa (!isLocked[msg.sender]) hay không, cho thấy không có việc rút tiền nào khác từ cùng một tài khoản đang diễn ra.
Nếu tài khoản không bị khóa, hợp đồng tiếp tục với việc thay đổi trạng thái và tương tác bên ngoài. Sau khi thay đổi trạng thái và tương tác bên ngoài, tài khoản được mở khóa lại, cho phép các lần rút tiền trong tương lai.
Các Loại Tấn Công Reentrancy
Người cầm ví da nâu và đồng Ethereum
Nói chung, có ba loại tấn công reentrancy chính dựa trên bản chất khai thác của chúng.
- Tấn công reentrancy đơn: Trong trường hợp này, hàm dễ bị tổn thương mà kẻ tấn công liên tục gọi là cùng một hàm dễ bị tổn thương với cổng reentrancy. Tấn công ở trên là một ví dụ về tấn công reentrancy đơn, có thể dễ dàng ngăn chặn bằng cách triển khai các kiểm tra và khóa phù hợp trong mã.
- Tấn công chéo hàm: Trong kịch bản này, kẻ tấn công sử dụng một hàm dễ bị tổn thương để gọi một hàm khác trong cùng một hợp đồng mà chia sẻ trạng thái với hàm dễ bị tổn thương. Hàm thứ hai được gọi bởi kẻ tấn công có một số tác động mong muốn, làm cho nó hấp dẫn hơn để khai thác. Tấn công này phức tạp hơn và khó phát hiện hơn, vì vậy cần có các kiểm tra và khóa nghiêm ngặt trên các hàm liên kết để giảm thiểu nó.
- Tấn công chéo hợp đồng: Tấn công này xảy ra khi một hợp đồng bên ngoài tương tác với một hợp đồng dễ bị tổn thương. Trong quá trình tương tác này, trạng thái của hợp đồng dễ bị tổn thương được gọi trong hợp đồng bên ngoài trước khi nó được cập nhật hoàn toàn. Nó thường xảy ra khi nhiều hợp đồng chia sẻ cùng một biến và một số hợp đồng cập nhật biến được chia sẻ một cách không an toàn. Cần phải triển khai các giao thức giao tiếp an toàn giữa các hợp đồng và thực hiện kiểm toán hợp đồng thông minh định kỳ để giảm thiểu tấn công này.
Tấn công reentrancy có thể biểu hiện dưới nhiều hình thức khác nhau và do đó yêu cầu các biện pháp cụ thể để ngăn chặn từng loại.
-
Tấn công reentrancy là gì?
Tấn công reentrancy là một loại tấn công trong đó một hợp đồng thông minh dễ bị tổn thương bị khai thác bởi một hợp đồng độc hại, cho phép kẻ tấn công gọi lại nhiều lần hàm dễ bị tổn thương trước khi nó hoàn thành việc thực thi, dẫn đến việc rút cạn các quỹ. -
Làm thế nào để phát hiện một hợp đồng thông minh dễ bị tấn công reentrancy?
Để phát hiện một hợp đồng thông minh dễ bị tấn công reentrancy, bạn cần kiểm tra xem các hàm có thực hiện các cuộc gọi bên ngoài trước khi cập nhật trạng thái của hợp đồng hay không. Nếu có, hợp đồng đó có thể dễ bị tấn công reentrancy. -
Các biện pháp phòng ngừa tấn công reentrancy là gì?
Các biện pháp phòng ngừa bao gồm triển khai mẫu “kiểm tra-tác động-tương tác”, sử dụng các khóa để ngăn chặn các cuộc gọi lại không mong muốn, và thực hiện các kiểm toán hợp đồng thông minh định kỳ. -
Tấn công reentrancy có thể xảy ra trên các blockchain khác ngoài Ethereum không?
Đúng, tấn công reentrancy có thể xảy ra trên bất kỳ blockchain nào sử dụng hợp đồng thông minh, không chỉ giới hạn ở Ethereum. -
Làm thế nào để người dùng bảo vệ tài sản của mình khỏi tấn công reentrancy?
Người dùng nên chọn các dự án và nền tảng đã được kiểm toán bảo mật, tránh tương tác với các hợp đồng thông minh không rõ nguồn gốc, và luôn theo dõi các cập nhật về bảo mật từ các dự án mà họ tham gia. -
Vụ hack Ethereum DAO có liên quan đến tấn công reentrancy như thế nào?
Vụ hack Ethereum DAO là một ví dụ điển hình về tấn công reentrancy, trong đó kẻ tấn công đã khai thác một lỗ hổng trong hợp đồng thông minh của DAO để liên tục rút tiền trước khi số dư được cập nhật, dẫn đến việc mất hơn 60 triệu đô la ETH. -
Có thể sử dụng các công cụ nào để phát hiện và ngăn chặn tấn công reentrancy?
Các công cụ như Mythril, Slither, và các dịch vụ kiểm toán hợp đồng thông minh từ các công ty bảo mật như CertiK và Quantstamp có thể giúp phát hiện và ngăn chặn tấn công reentrancy.
Bảo Vệ Khỏi Tấn Công Reentrancy
Tấn công reentrancy đã gây ra thiệt hại tài chính đáng kể và làm suy giảm lòng tin vào các ứng dụng blockchain. Để bảo vệ các hợp đồng, các nhà phát triển phải thực hiện các thực hành tốt nhất một cách cẩn thận để tránh các lỗ hổng reentrancy.
Họ cũng nên triển khai các mẫu rút tiền an toàn, sử dụng các thư viện đáng tin cậy và thực hiện các kiểm toán kỹ lưỡng để củng cố thêm khả năng phòng thủ của hợp đồng thông minh. Tất nhiên, việc cập nhật thông tin về các mối đe dọa mới nổi và chủ động trong nỗ lực bảo mật có thể đảm bảo họ duy trì tính toàn vẹn của các hệ sinh thái blockchain.