Zalecenia dotyczące bezpieczeństwa inteligentnych kontraktów Ethereum

blog 1AktualnościDevelopersEnterpriseBlockchain ExplainedWydarzenia i konferencjePrasaBiuletyny

Zapisz się do naszego newslettera.

Adres e-mail

Szanujemy twoją prywatność

HomeBlogBlockchain Development

Zalecenia dotyczące bezpieczeństwa inteligentnych kontraktów Ethereum

Od obsługi połączeń zewnętrznych po schematy zobowiązań, oto ponad 10 wzorców zabezpieczeń inteligentnych kontraktów, których należy przestrzegać podczas tworzenia na Ethereum. Autor: ConsenSys 10 lipca 2020 Opublikowane 10 lipca 2020

Zalecenia dotyczące bezpieczeństwa inteligentnych kontraktów Ethereum

Jak omówiliśmy w Smart Contract Security Mindset, czujny programista Ethereum zawsze ma na uwadze pięć zasad:

  • Przygotuj się na porażkę
  • Rozłóż ostrożnie
  • Postaraj się, aby umowy były proste
  • Bądź na bieżąco
  • Bądź świadomy specyfiki EVM

W tym poście zagłębimy się w specyfikę EVM i przejdziemy przez listę wzorców, których należy przestrzegać podczas opracowywania dowolnego systemu inteligentnych kontraktów w Ethereum. Ten kawałek jest przeznaczony głównie dla średnio zaawansowanych programistów Ethereum. Jeśli nadal jesteś na wczesnym etapie eksploracji, zapoznaj się z programem dla programistów blockchain na żądanie w ConsenSys Academy. 

OK, zanurzmy się.

Połączenia zewnętrzne

Zachowaj ostrożność podczas wykonywania połączeń zewnętrznych

Połączenia z niezaufanymi inteligentnymi umowami mogą spowodować kilka nieoczekiwanych zagrożeń lub błędów. Połączenia zewnętrzne mogą wykonywać złośliwy kod w tej umowie lub jakiejkolwiek innej umowie, od której zależy. Dlatego traktuj każde połączenie zewnętrzne jako potencjalne zagrożenie bezpieczeństwa. Jeśli nie jest możliwe lub niepożądane usuwanie połączeń zewnętrznych, skorzystaj z zaleceń w dalszej części tej sekcji, aby zminimalizować niebezpieczeństwo.

Oznacz niezaufane umowy

Podczas interakcji z zewnętrznymi kontraktami nazwij swoje zmienne, metody i interfejsy kontraktów w sposób, który jasno pokaże, że interakcja z nimi jest potencjalnie niebezpieczna. Dotyczy to twoich własnych funkcji, które wywołują umowy zewnętrzne.

// zły Bank.withdraw (100); // Nie wiadomo, czy zaufana, czy niezaufana funkcja makeWithdrawal (uint amount) {// Nie jest jasne, czy ta funkcja jest potencjalnie niebezpieczna Bank.withdraw (amount); } // dobry UntrustedBank.withdraw (100); // niezaufane połączenie zewnętrzne TrustedBank.withdraw (100); // zewnętrzna, ale zaufana umowa bankowa obsługiwana przez funkcję XYZ Corp makeUntrustedWithdrawal (kwota uint) {UntrustedBank.withdraw (kwota); } Język kodu: PHP (php)

Unikaj zmian stanu po połączeniach zewnętrznych

Niezależnie od tego, czy używasz wywołań surowych (w postaci someAddress.call ()), czy wywołań kontraktów (w postaci ExternalContract.someMethod ()), załóż, że może zostać wykonany złośliwy kod. Nawet jeśli ExternalContract nie jest złośliwy, złośliwy kod może zostać wykonany przez wszystkie wywoływane kontrakty.

Jednym ze szczególnych zagrożeń jest to, że złośliwy kod może przejąć kontrolę, prowadząc do luk w zabezpieczeniach z powodu ponownego wejścia. (Widzieć Ponowne wejście dla pełniejszego omówienia tego problemu).

Jeśli dzwonisz do niezaufanej umowy zewnętrznej, unikaj zmian stanu po rozmowie. Ten wzór jest czasami nazywany Wzorzec interakcji sprawdzających efekty.

Widzieć SWC-107

Nie używaj transferu () ani wysyłania ().

.transfer () and.send () przekazuje dokładnie 2300 gazu do odbiorcy. Celem tego zakodowanego na stałe stypendium gazowego było zapobieganie podatności na ponowne wejście, ale ma to sens tylko przy założeniu, że koszty gazu są stałe. EIP 1884, który był częścią hard fork w Stambule, zwiększył koszt gazu w operacji SLOAD. Spowodowało to, że funkcja rezerwowa kontraktu kosztowała ponad 2300 gazu. Zalecamy zaprzestanie używania.transfer () i.send () i zamiast tego użycie.call ().

// zły kontrakt Podatny {funkcja wycofania (uint256 kwota) zewnętrzna {// Przekazuje 2300 gazu, co może nie wystarczyć, jeśli odbiorca // jest kontraktem i zmieniają się koszty gazu. msg.sender.transfer (kwota); }} // dobry kontrakt Naprawiono {funkcja wycofania (uint256 kwota) zewnętrzna {// Przekazuje cały dostępny gaz. Pamiętaj, aby sprawdzić zwracaną wartość! (bool sukces,) = msg.sender.call.value (kwota) (""); wymagają (powodzenie, "Transfer nie powiódł się."); }} Język kodu: JavaScript (javascript)

Zauważ, że call () nie robi nic, aby złagodzić ataki reentrancy, więc należy podjąć inne środki ostrożności. Aby zapobiec atakom ponownego wejścia, użyj rozszerzenia Wzorzec interakcji sprawdzających efekty.

Obsługa błędów w połączeniach zewnętrznych

Solidity oferuje niskopoziomowe metody wywołań, które działają na surowych adresach: address.call (), address.callcode (), address.delegatecall () i address.send (). Te metody niskiego poziomu nigdy nie zgłaszają wyjątku, ale zwrócą wartość false, jeśli wywołanie napotka wyjątek. Z drugiej strony wywołania kontraktu (np. ExternalContract.doSomething ()) automatycznie propagują rzut (na przykład ExternalContract.doSomething () również wyrzuci, jeśli doSomething () rzuci).

Jeśli zdecydujesz się użyć niskopoziomowych metod wywołania, upewnij się, że uwzględnisz możliwość niepowodzenia wywołania, sprawdzając zwracaną wartość.

// zły someAddress.send (55); someAddress.call.value (55) (""); // jest to podwójnie niebezpieczne, ponieważ przekazuje cały pozostały gaz i nie sprawdza wyniku someAddress.call.value (100) (bytes4 (sha3 ("kaucja()"))); // jeśli depozyt zgłosi wyjątek, surowe wywołanie () zwróci tylko false i transakcja NIE zostanie cofnięta // good (bool success,) = someAddress.call.value (55) (""); if (! success) {// obsłuż kod błędu} ExternalContract (someAddress) .deposit.value (100) (); Język kodu: JavaScript (javascript)

Widzieć SWC-104

Preferuj opcję „pull over push” w przypadku połączeń zewnętrznych

Połączenia zewnętrzne mogą się nie powieść przypadkowo lub celowo. Aby zminimalizować szkody spowodowane takimi awariami, często lepiej jest wyodrębnić każde połączenie zewnętrzne jako jego własną transakcję, którą może zainicjować odbiorca połączenia. Jest to szczególnie istotne w przypadku płatności, w przypadku których lepiej jest pozwolić użytkownikom na wypłatę środków niż automatyczne ich przekazywanie. (Zmniejsza to również prawdopodobieństwo problemy z limitem gazu.) Unikaj łączenia wielu przelewów ethernetowych w jednej transakcji.

// aukcja złych kontraktów {address najwyższyBidder; uint najwyższyBid; funkcja bid () payable {require (msg.value >= najwyższa stawka); if (najwyższyBidder! = adres (0)) {(sukces bool,) = najwyższaBidder.call.value (najwyższaBid) (""); wymagają (sukces); // jeśli to wywołanie nie powiedzie się, nikt inny nie może licytować} najwyższyBidder = msg.sender; najwyższaBid = msg.value; }} // dobra aukcja kontraktowa {address najwyższyBidder; uint najwyższyBid; mapowanie (adres => uint) zwroty; funkcja bid () płatna zewnętrzna {require (msg.value >= najwyższa stawka); if (najwyższyBidder! = address (0)) {refunds [najwyższyBidder] + = najwyższyBid; // zapisz zwrot, o który może ubiegać się ten użytkownik} najwyższyBidder = msg.sender; najwyższaBid = msg.value; } function removeRefund () external {uint refund = refunds [msg.sender]; zwroty [msg.sender] = 0; (bool success,) = msg.sender.call.value (refund) (""); wymagają (sukces); }} Język kodu: JavaScript (javascript)

Widzieć SWC-128

Nie deleguj połączeń na niezaufany kod

Funkcja delegatecall wywołuje funkcje z innych kontraktów, tak jakby należały do ​​kontraktu wywołującego. W ten sposób odbiorca może zmienić stan adresu wywołującego. To może być niepewne. Poniższy przykład pokazuje, jak użycie funkcji delegatecall może doprowadzić do zniszczenia kontraktu i utraty jego równowagi.

Contract Destructor {function doWork () external {selfdestruct (0); }} Pracownik kontraktowy {function doWork (address _internalWorker) public {// unsafe _internalWorker.delegatecall (bytes4 (keccak256 ("wykonać pracę()"))); }} Język kodu: JavaScript (javascript)

Jeśli Worker.doWork () zostanie wywołana z adresem wdrożonego kontraktu Destructor jako argumentem, kontrakt Worker ulegnie samozniszczeniu. Deleguj wykonanie tylko do zaufanych kontraktów, a nigdy na adres podany przez użytkownika.

Ostrzeżenie

Nie zakładaj, że kontrakty są tworzone z zerowym saldem. Atakujący może wysłać eter na adres kontraktu przed jego utworzeniem. Kontrakty nie powinny zakładać, że ich stan początkowy zawiera saldo zerowe. Widzieć wydanie 61 po więcej szczegółów.

Widzieć SWC-112

Pamiętaj, że eter może zostać przymusowo wysłany na konto

Uważaj na kodowanie niezmiennika, który ściśle sprawdza równowagę kontraktu.

Atakujący może wymusić wysłanie eteru na dowolne konto. Nie można temu zapobiec (nawet za pomocą funkcji rezerwowej, która wykonuje revert ()).

Atakujący może to zrobić, tworząc kontrakt, finansując go 1 wei i wywołując autodestrukcję (ofiaraAddress). Żaden kod nie jest wywoływany w adresie ofiary, więc nie można temu zapobiec. Dotyczy to również nagrody blokowej, która jest wysyłana na adres górnika, którym może być dowolny adres.

Ponadto, ponieważ adresy umów mogą być wstępnie obliczane, ether można wysłać na adres przed wdrożeniem umowy.

Widzieć SWC-132

Pamiętaj, że dane w łańcuchu są publiczne

Wiele aplikacji wymaga, aby przesłane dane były prywatne do pewnego momentu, aby działały. Gry (np. Nożyczki do papieru, kamień, łańcuch) i mechanizmy aukcyjne (np. Oferta zamknięta Aukcje Vickrey) to dwie główne kategorie przykładów. Jeśli tworzysz aplikację, w której problemem jest prywatność, upewnij się, że nie wymaga się od użytkowników zbyt wczesnego publikowania informacji. Najlepszą strategią jest użycie schematy zobowiązań z oddzielnymi fazami: najpierw zatwierdzenie przy użyciu skrótu wartości, aw późniejszej fazie ujawnienie wartości.

Przykłady:

  • W nożyczkach z papieru do kamienia, wymagaj od obu graczy, aby najpierw przesłali skrót ich zamierzonego ruchu, a następnie wymagaj, aby obaj gracze wykonali ruch; jeśli przesłany ruch nie zgadza się z hashem, wyrzuć go.
  • W aukcji wymagaj od graczy, aby w fazie początkowej przesłali skrót ich wartości oferty (wraz z kaucją wyższą niż ich wartość oferty), a następnie przedstawili wartość oferty aukcyjnej w drugiej fazie.
  • Podczas tworzenia aplikacji, która opiera się na generatorze liczb losowych, kolejność powinna zawsze być następująca: (1) gracze wykonują ruchy, (2) generują losową liczbę, (3) gracze otrzymują wypłatę. Wiele osób aktywnie poszukuje generatorów liczb losowych; Obecne najlepsze w swojej klasie rozwiązania obejmują nagłówki bloków Bitcoin (zweryfikowane przez http://btcrelay.org), schematy haszowania-zatwierdzania-ujawniania (tj. jedna strona generuje liczbę, publikuje swój skrót, aby „zatwierdzić” wartość, a następnie ujawnia wartość później) i RANDAO. Ponieważ Ethereum jest protokołem deterministycznym, nie można używać żadnej zmiennej w protokole jako nieprzewidywalnej liczby losowej. Należy również pamiętać, że górnicy do pewnego stopnia kontrolują wartość block.blockhash ()*.

Uważaj na możliwość, że niektórzy uczestnicy mogą „porzucić połączenie z Internetem” i nie wrócić

Nie uzależniaj procesu zwrotu pieniędzy lub roszczenia od konkretnej strony wykonującej określoną czynność bez innego sposobu na odzyskanie środków. Na przykład w grze kamień-papier-nożyce częstym błędem jest nie wypłacanie wypłaty, dopóki obaj gracze nie wykonają swoich ruchów; jednak złośliwy gracz może „zasmucić” drugiego, po prostu nigdy nie wykonując swojego ruchu – w rzeczywistości, jeśli gracz widzi ujawniony ruch innego gracza i stwierdzi, że przegrał, nie ma żadnego powodu, by przesyłać własne posunięcie. Ta kwestia może również pojawić się w kontekście rozliczenia kanału państwowego. Gdy takie sytuacje stanowią problem, (1) zapewniają sposób obejścia nieuczestniczących uczestników, na przykład przez określony czas, oraz (2) rozważają dodanie dodatkowej zachęty ekonomicznej dla uczestników do przekazywania informacji we wszystkich sytuacjach, w których się znajdują. powinien to zrobić.

Uważaj na negację najbardziej ujemnej liczby całkowitej ze znakiem

Solidity udostępnia kilka typów do pracy z liczbami całkowitymi ze znakiem. Podobnie jak w większości języków programowania, w Solidity liczba całkowita ze znakiem z N bitami może reprezentować wartości od -2 ^ (N-1) do 2 ^ (N-1) -1. Oznacza to, że nie ma dodatniego odpowiednika dla MIN_INT. Negacja jest implementowana jako znajdowanie dopełnienia do dwóch liczby, czyli negacja liczby najbardziej ujemnej da taką samą liczbę. Dotyczy to wszystkich typów liczb całkowitych ze znakiem w Solidity (int8, int16,…, int256).

Negacja kontraktu {funkcja negate8 (int8 _i) public pure zwraca (int8) {return -_i; } funkcja negate16 (int16 _i) public pure zwraca (int16) {return -_i; } int8 public a = negate8 (-128); // -128 int16 public b = negate16 (-128); // 128 int16 public c = negate16 (-32768); // -32768} Język kodu: PHP (php)

Jednym ze sposobów rozwiązania tego problemu jest sprawdzenie wartości zmiennej przed negacją i zgłoszenie, jeśli jest równa MIN_INT. Inną opcją jest upewnienie się, że najbardziej ujemna liczba nigdy nie zostanie osiągnięta przez użycie typu o większej pojemności (np. Int32 zamiast int16).

Podobny problem z typami int występuje, gdy wartość MIN_INT jest mnożona lub dzielona przez -1.

Czy Twój kod blockchain jest bezpieczny?? 

Mamy nadzieję, że te zalecenia były pomocne. Jeśli Ty i Twój zespół przygotowujesz się do uruchomienia lub nawet na początku cyklu rozwoju i potrzebujesz sprawdzenia poprawności inteligentnych kontraktów, skontaktuj się z naszym zespołem inżynierów ds. Bezpieczeństwa w ConsenSys Diligence. Jesteśmy tutaj, aby pomóc Ci uruchomić i utrzymać aplikacje Ethereum ze 100% pewnością. 

Zarezerwuj kontrolę bezpieczeństwa

Zarezerwuj 1-dniową recenzję z naszym zespołem ekspertów ds. Bezpieczeństwa blockchain. Zarezerwuj już dziś Bezpieczeństwo Inteligentne umowyNewsletter Zapisz się do naszego newslettera, aby otrzymywać najnowsze wiadomości o Ethereum, rozwiązania dla przedsiębiorstw, zasoby dla programistów i nie tylko.Jak zbudować udany produkt BlockchainWebinar

Jak zbudować udany produkt Blockchain

Jak skonfigurować i uruchomić węzeł EthereumWebinar

Jak skonfigurować i uruchomić węzeł Ethereum

Jak zbudować własny interfejs API EthereumWebinar

Jak zbudować własny interfejs API Ethereum

Jak stworzyć token społecznościowyWebinar

Jak stworzyć token społecznościowy

Korzystanie z narzędzi bezpieczeństwa w tworzeniu inteligentnych kontraktówWebinar

Korzystanie z narzędzi bezpieczeństwa w tworzeniu inteligentnych kontraktów

Przyszłość finansów, aktywów cyfrowych i DeFiWebinar

Przyszłość finansów: aktywa cyfrowe i DeFi

Mike Owergreen Administrator
Sorry! The Author has not filled his profile.
follow me
Like this post? Please share to your friends:
Adblock
detector
map