SicherheitslĂŒcken in Etherium Smart-VertrĂ€gen. Codebeispiele

Mit diesem Beitrag beginne ich eine Reihe von Artikeln zur Sicherheit von Ethereum-Smart-VertrĂ€gen. Ich denke, dieses Thema ist sehr relevant, da die Anzahl der Entwickler wie eine Lawine wĂ€chst und es niemanden gibt, der vor dem "Rechen" gerettet werden kann. TschĂŒss - Übersetzungen ...

1. Scannen von Live Ethereum-VertrÀgen auf nicht aktivierte Sendefehler


Original - Scannen von Live Ethereum-VertrĂ€gen fĂŒr "Unchecked-Send ..."


Autoren: Zikai Alex Wen und Andrew Miller

Die intelligente Vertragsprogrammierung von Ethereum ist bekanntermaßen fehleranfĂ€llig [1] . Vor kurzem haben wir das mehrere gesehen
High-End-Smart-VertrÀge wie King of the Ether und The DAO-1.0 enthielten Schwachstellen, die durch Programmierfehler verursacht wurden.

Seit MÀrz 2015 werden intelligente Vertragsprogrammierer vor spezifischen Programmiergefahren gewarnt, die auftreten können, wenn VertrÀge Nachrichten aneinander senden [6] .

In mehreren ProgrammierhandbĂŒchern wird empfohlen, hĂ€ufige Fehler zu vermeiden (in White Papers Ethereum [3] und in einem unabhĂ€ngigen Handbuch von UMD [2] ). Obwohl diese Gefahren verstĂ€ndlich genug sind, um sie zu vermeiden, sind die Folgen eines solchen Fehlers schrecklich: Geld kann blockiert, verloren oder gestohlen werden.

Wie hÀufig treten Fehler aufgrund dieser Gefahren auf? Gibt es verletzlichere, aber lebende Ethereum-Blockchain-VertrÀge? In diesem Artikel beantworten wir diese Frage, indem wir VertrÀge in der Live-Blockchain von Ethereum mit dem von uns entwickelten neuen Analysetool analysieren.


Was ist der nicht aktivierte Sendefehler?


Um einen Sendezeitvertrag an eine andere Adresse zu senden, verwenden Sie am einfachsten das SchlĂŒsselwort send . Dies fungiert als fĂŒr jedes Objekt definierte Methode. Das folgende Codefragment befindet sich beispielsweise in einem intelligenten Vertrag, der ein Brettspiel implementiert.


/*** Listing 1 ***/ if (gameHasEnded && !( prizePaidOut ) ) { winner.send(1000); //    prizePaidOut = True; } 

Das Problem hierbei ist, dass die Sendemethode möglicherweise fehlschlÀgt. Wenn es nicht funktioniert, erhÀlt der Gewinner das Geld nicht, die Variable pricePaidOut wird jedoch auf True gesetzt.

Es gibt zwei verschiedene FĂ€lle, in denen die Funktion won.send () möglicherweise fehlschlĂ€gt. Wir werden den Unterschied zwischen ihnen spĂ€ter analysieren. Der erste Fall ist, dass die Gewinneradresse ein Vertrag ist (kein Benutzerkonto) und der Code fĂŒr diesen Vertrag eine Ausnahme auslöst (z. B. wenn zu viel „Gas“ verwendet wird). Wenn ja, dann ist es in diesem Fall vielleicht ein "Fehler des Gewinners". Der zweite Fall ist weniger offensichtlich. Die virtuelle Ethereum-Maschine verfĂŒgt ĂŒber eine begrenzte Ressource namens " Callstack " (Call-Stack-Tiefe). Diese Ressource kann von einem anderen Vertragscode verwendet werden, der zuvor in einer Transaktion ausgefĂŒhrt wurde. Wenn der Callstack zum Zeitpunkt der AusfĂŒhrung des Sendebefehls bereits aufgebraucht war, schlĂ€gt der Befehl fehl, unabhĂ€ngig davon, wie der Gewinner ermittelt wird. Der Preis des Gewinners wird unverschuldet vernichtet!



Wie kann dieser Fehler vermieden werden?

Die Ethereum-Dokumentation enthĂ€lt eine kurze Warnung zu dieser potenziellen Gefahr [3] : "Bei Verwendung von send besteht eine gewisse Gefahr - die Übertragung schlĂ€gt fehl, wenn die Tiefe des Aufrufstapels 1024 betrĂ€gt (dies kann immer vom Anrufer verursacht werden), und schlĂ€gt auch fehl, wenn der EmpfĂ€nger "Gas" endet. Um eine sichere Übertragung zu gewĂ€hrleisten, ĂŒberprĂŒfen Sie daher immer den RĂŒckgabewert von " Senden" oder besser: Verwenden Sie eine Vorlage, in der der EmpfĂ€nger Geld abhebt. "

Zwei SĂ€tze. Der erste besteht darin, den RĂŒckgabewert von send zu ĂŒberprĂŒfen, um festzustellen , ob er erfolgreich abgeschlossen wurde. Wenn dies nicht der Fall ist, lösen Sie eine Ausnahme aus, um den Status zurĂŒckzusetzen.


  /*** Listing 2 ***/ if (gameHasEnded && !( prizePaidOut ) ) { if (winner.send(1000)) prizePaidOut = True; else throw; } 

Dies ist eine angemessene Lösung fĂŒr das aktuelle Beispiel, aber nicht immer die richtige Entscheidung. Angenommen, wir Ă€ndern unser Beispiel so, dass der Gewinner und der Verlierer nach dem Spiel ihr Vermögen zurĂŒckdrehen. Eine offensichtliche Anwendung einer „formalen“ Lösung wĂ€re die folgende:


 /*** Listing 3 ***/ if (gameHasEnded && !( prizePaidOut ) ) { if (winner.send(1000) && loser.send(10)) prizePaidOut = True; else throw; } 

Dies ist jedoch ein Fehler, da dadurch eine zusĂ€tzliche SicherheitsanfĂ€lligkeit entsteht. WĂ€hrend dieser Code den Gewinner vor einem Callstack- Angriff schĂŒtzt, macht er Gewinner und Verlierer auch fĂŒr einander anfĂ€llig. In diesem Fall möchten wir einen Callstack- Angriff verhindern, aber die AusfĂŒhrung fortsetzen, wenn der Sendebefehl aus irgendeinem Grund fehlschlĂ€gt.

Daher ist es selbst die beste bewĂ€hrte Methode (empfohlen in unserem Ethereum and Serpent Programmer's Guide, obwohl dies auch fĂŒr Solidity gilt), nach einer Callstack- Ressource zu suchen . Wir können ein Makro callStackIsEmpty () definieren , das genau dann einen Fehler zurĂŒckgibt, wenn callstack leer ist.


 /*** Listing 4 ***/ if (gameHasEnded && !( prizePaidOut ) ) { if (callStackIsEmpty()) throw; winner.send(1000) loser.send(10) prizePaidOut = True; } 

Noch besser ist, dass die Empfehlung aus der Ethereum-Dokumentation - „Verwenden Sie eine Vorlage, in der der EmpfĂ€nger das Geld nimmt“ - etwas kryptisch ist, aber eine ErklĂ€rung enthĂ€lt. Der Vorschlag besteht darin, Ihren Code so zu reorganisieren, dass die Auswirkung des Sendefehlers isoliert ist und jeweils nur einen EmpfĂ€nger betrifft. Das Folgende ist ein Beispiel fĂŒr diesen Ansatz. Dieser Tipp ist jedoch auch ein Anti-Muster. Er ĂŒbernimmt die Verantwortung fĂŒr die ÜberprĂŒfung des Callstacks gegenĂŒber den EmpfĂ€ngern selbst, wodurch es möglich wird, in dieselbe Falle zu tappen.


 /*** Listing 5 ***/ if (gameHasEnded && !( prizePaidOut ) ) { accounts[winner] += 1000 accounts[loser] += 10 prizePaidOut = True; } ... function withdraw(amount) { if (accounts[msg.sender] >= amount) { msg.sender.send(amount); accounts[msg.sender] -= amount; } } 

Viele hochentwickelte intelligente VertrĂ€ge sind anfĂ€llig. Die Lotterie "König der Luft des Throns" ist der bekannteste Fall dieses Fehlers [4] . Dieser Fehler wurde erst bemerkt, als die Menge von 200 Ethern (im Wert von mehr als 2000 US-Dollar zum heutigen Preis) nicht den legitimen Gewinner der Lotterie erhalten konnte. Der entsprechende Code in King of the Ether Ă€hnelt dem Code in Listing 2. GlĂŒcklicherweise konnte der Vertragsentwickler in diesem Fall die nicht verwandte Funktion im Vertrag als „manuelle Überschreibung“ verwenden, um die festgefahrenen Gelder freizugeben. Ein weniger gewissenhafter Administrator könnte dieselbe Funktion verwenden, um die Sendung zu stehlen!


Fortsetzung des Scannens von Live Ethereum-VertrÀgen auf nicht aktivierte Sendefehler. Teil 2

Source: https://habr.com/ru/post/de430602/


All Articles