paint-brush
Behebung der Sicherheitslücke bezüglich Ganzzahlüberlauf/-unterlauf in Smart Contractsvon@dansierrasam79
2,180 Lesungen
2,180 Lesungen

Behebung der Sicherheitslücke bezüglich Ganzzahlüberlauf/-unterlauf in Smart Contracts

von Daniel Chakraborty9m2023/02/11
Read on Terminal Reader
Read this story w/o Javascript

Zu lang; Lesen

Datentypen sind etwas, für dessen Spezifizierung sich Programmierer entweder die Zeit nehmen oder nicht, abhängig von der Programmiersprache, in der sie programmieren. Datentypen sind wichtig für wichtige arithmetische Operationen, haben aber für jede Berechnung einen begrenzten Bereich. Das beliebteste Beispiel für einen Ganzzahlüberlauf in der realen Welt tritt bei Fahrzeugen auf, bei denen der Wert der zurückgelegten Meilen auf 000000 zurückgesetzt wird.
featured image - Behebung der Sicherheitslücke bezüglich Ganzzahlüberlauf/-unterlauf in Smart Contracts
Daniel Chakraborty HackerNoon profile picture


Fast alle von uns haben Google Sheets oder Microsoft Excel verwendet, um Daten für Berechnungen einzugeben. Angenommen, Sie möchten die Namen der Mitarbeiter, ihre Telefonnummern, Titel und das Gehalt, das sie verdienen, eingeben.


In seiner einfachsten Form würde ein Datensatz oder Fall in Tabellen oder Excel so aussehen:

Ein Datensatz oder Fall in Excel oder Google Sheets

Wie Sie sehen, bestehen sowohl der Name als auch der Titel des Mitarbeiters aus Text, während die Telefonnummer und das Gehalt aus einer Zahlenfolge bestehen.


Aus semantischer Sicht verstehen wir als Menschen also, was diese Felder in der realen Welt bedeuten und können zwischen ihnen unterscheiden.


Auch wenn man keinen Abschluss in Informatik braucht, um den Unterschied zu erkennen, stellt sich die Frage: Wie verarbeitet ein Compiler oder Interpreter diese Daten?

Datentypen

Hier kommen Datentypen ins Spiel, für deren Angabe sich Programmierer entweder die Zeit nehmen oder nicht, abhängig von der Programmiersprache, in der sie programmieren.


Mit anderen Worten: Die Datenpunkte unter dem Namen und dem Titel des Mitarbeiters werden als Zeichenfolgen bezeichnet. Natürlich ist das Gehalt eindeutig eine ganze Zahl, da es keine Dezimalstellen gibt. Einfach ausgedrückt handelt es sich dabei um Datentypen, die beim Codieren als solche deklariert werden müssen, damit nur die richtigen, mit diesem Datentyp verknüpften Vorgänge ausgeführt werden.


So deklarieren wir einen ganzzahligen Datentyp in Solidity:

Allerdings enthält das Feld „Telefonnummer“ in der Tabelle oben einen Datenpunkt, der als eindeutige Zeichenfolge verwendet wird, aber diese Diskussion wird für einen anderen Tag stattfinden. Im Moment konzentrieren wir uns auf den primitiven Datentyp, mit dem wir alle grundlegende Arithmetik durchgeführt haben.


Ja, wir sprechen über den ganzzahligen Datentyp, der zwar für wichtige arithmetische Operationen wichtig ist, aber für jede Berechnung einen begrenzten Bereich hat.

Warum kommt es zu einem Ganzzahlüberlauf/-unterlauf?

Das wohl bekannteste Beispiel für einen Ganzzahlüberlauf in der realen Welt tritt bei Fahrzeugen auf. Diese auch als Kilometerzähler bezeichneten Geräte zeichnen im Allgemeinen auf, wie viele Kilometer ein Fahrzeug zurückgelegt hat.


Was passiert also, wenn der Wert der zurückgelegten Meilen den vorzeichenlosen Ganzzahlwert 999999 in einem sechsstelligen Kilometerzähler erreicht?


Sobald eine weitere Meile hinzukommt, sollte dieser Wert im Idealfall 1.000.000 erreichen, oder? Dies geschieht jedoch nicht, da eine siebte Ziffer vorgesehen ist.


Stattdessen wird der Wert der zurückgelegten Meilen auf 000000 zurückgesetzt, wie unten gezeigt:

Ganzzahlüberlauf in einem Kilometerzähler


Da die siebte Ziffer nicht verfügbar ist, führt dies per Definition zu einem „Überlauf“, da der genaue Wert nicht dargestellt wird.


Du verstehst das Bild, oder?


Umgekehrt kann auch das Gegenteil eintreten, auch wenn dies nicht so häufig vorkommt. Mit anderen Worten, wenn der aufgezeichnete Wert kleiner ist als der kleinste verfügbare Wert im Bereich, was auch als „Unterlauf“ bezeichnet wird.


Wie wir alle wissen, speichern Computer Ganzzahlen als binäres Äquivalent im Speicher. Nehmen wir der Einfachheit halber an, dass Sie ein 8-Bit-Register verwenden.


Wenn Sie also die vorzeichenlose Ganzzahl 511 speichern möchten, würde diese wie folgt aufgeteilt:


= 2⁸*1 + 2⁷*1 + 2⁶*1 + 2⁵*1 + 2⁴*1 + 2³*1 + 2²*1 + 2¹*1 + 2⁰*1

= 256 + 128 + 64 + 32 + 16 + 8 + 4 + 2 + 1

= 111111111


Wobei jedes Bit 1 ist und wie Sie sehen, können Sie keinen höheren Wert speichern.


Wenn Sie hingegen die Zahl 0 im 8-Bit-Register speichern möchten, würde das so aussehen:


= 2⁸*0 + 2⁷*0 + 2⁶*0 + 2⁵*0 + 2⁴*0 + 2³*0 + 2²*0 + 2¹*0 + 2⁰*0

= 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0

= 000000000


Wobei jedes Bit 0 ist, was Ihnen sagen sollte, dass Sie keinen niedrigeren Wert speichern können.


Mit anderen Worten: Der zulässige Ganzzahlbereich für ein solches 8-Bit-Register liegt zwischen 0 und 511. Ist es also möglich, die Ganzzahl 512 oder -1 in einem solchen Register zu speichern?


Natürlich nicht. Als Ergebnis speichern Sie einen Wert, der dem Rücksetzwert der zurückgelegten Meilen im Beispiel des Kilometerzählers ähnelt, jedoch als Binärwerte.


Natürlich bräuchte man Register mit ein paar Bits mehr, um eine solche Zahl bequem unterzubringen. Andernfalls riskieren Sie erneut die Situation eines Überlaufs.


Im Fall von vorzeichenbehafteten Ganzzahlen speichern wir auch negative Ganzzahlen. Wenn wir also versuchen, eine Zahl zu speichern, die kleiner als der akzeptierte Bereich oder kleiner als Null ist, wie oben gezeigt, kommt es zu einem Unterlauf.


Da es bei jeder Berechnung darum geht, deterministische Ergebnisse zu erhalten, kann dies im besten Fall ärgerlich sein, im schlimmsten Fall jedoch zu einem Verlust von Millionen führen. Insbesondere, wenn diese Integer-Überlauf- oder -Unterlauffehler in Smart Contracts auftreten.

Warum kann die Schwachstelle „Integer Overflow/Underflow“ so schädlich sein?

Während es Integer-Overflows und -Underflows schon seit Jahrzehnten gibt, hat ihre Existenz als Fehler in einem Smart Contract das Risiko erhöht. Wenn Angreifer solche Fehler ausnutzen, können sie dem Smart Contract große Mengen an Token entziehen.


Das wahrscheinlich erste Mal, dass ein Fehler dieser Art auftrat, war Block 74638, der Milliarden von Bitcoin für drei Adressen erzeugte. Es würde Stunden dauern, diesen Fehler mithilfe eines Soft Forks zu beheben, der den Block verworfen und damit die Transaktion ungültig gemacht hätte.


Zum einen wurden Transaktionen mit einem Wert von mehr als 21 Millionen Bitcoins abgelehnt. Dies war bei Überlauftransaktionen nicht anders, ähnlich wie bei der Transaktion, bei der so viel Geld auf die drei oben genannten Konten überwiesen wurde.


Allerdings kam es auch bei Ethereum-Smart-Contracts zu Integer-Überläufen und -Unterläufen, wobei BeautyChain ebenfalls ein prominentes Beispiel ist.


In diesem Fall enthielt der Smart Contract eine fehlerhafte Codezeile:


Dieser einzelne Code, der einen Ganzzahlüberlauf verursacht hat


Dadurch konnten die Angreifer theoretisch eine unbegrenzte Menge an BEC-Token erhalten, was theoretisch einem Wert von (2²⁵⁶)-1 entsprechen könnte.


Die anfällige Funktion „batchTransfer“.


Schauen wir uns nun ein weiteres Beispiel für einen Smart Contract an, bei dem ein ganzzahliger Unter-/Überlauf auftritt.

Behebung der Schwachstelle durch Ganzzahlüberlauf/-unterlauf in einem Smart Contract

Auf den ersten Blick interagieren in diesem Beispiel zwei Verträge, was zeigt, was im Fall eines Integer-Überlaufs passiert.


Wie Sie unten sehen können, ermöglicht Ihnen der TimeLock-Vertrag das Ein- und Auszahlen von Geldern, allerdings mit einem Unterschied: Letzteres können Sie erst nach einer bestimmten Zeitspanne ausführen. In diesem Fall können Sie Ihr Geld nur innerhalb einer Woche abheben.


Der TimeLock-Vertrag


Sobald Sie jedoch die Angriffsfunktion im Angriffsvertrag aufrufen, ist die Zeitsperre nicht mehr wirksam und der Angreifer kann den Guthabenbetrag sofort abheben.

Mit anderen Worten: Da mit der Anweisung type(uint).max+1-timeLock.locktime(address(this)) ein Ganzzahlüberlauf verursacht wird, wird die Zeitsperre aufgehoben.


Mittels Integer Overflow wird die Zeitsperre für die Einzahlung sofort aufgehoben


Wenn Sie beispielsweise beide Smart-Verträge mit dem obigen Code bereitgestellt haben, können Sie testen, ob die Zeitsperre gilt, indem Sie die Ein- und Auszahlungsfunktionen im TimeLock-Vertrag aufrufen, wie unten gezeigt:


Restbetrag nach Einzahlung von 2 ETH


Wie Sie sehen können, erhalten wir durch Auswahl eines Betrags von 2 Ether den oben gezeigten Smart-Contract-Saldo von 2 Ether:


2 ETH einzahlen


Insbesondere kann die spezifische Adresse, die den Saldo von 2 Ether hält, überprüft werden, indem man die Adresse in das Feld der Saldenfunktion einfügt und auf die Salden-Schaltfläche klickt:


Welche Adresse enthält 2 ETH?


Allerdings können Sie diese Gelder, wie oben erwähnt, aufgrund der bestehenden Zeitsperre noch nicht abheben. Wenn Sie auf die Konsole schauen, nachdem Sie auf die Auszahlungsfunktion geklickt haben, werden Sie einen Fehler finden, der durch das rote „x“-Symbol angezeigt wird. Wie Sie unten sehen können, lautet der im Vertrag angegebene Grund für diesen Fehler „Sperrzeit nicht abgelaufen“:


Fehler „Sperrzeit nicht abgelaufen“.


Schauen wir uns nun den bereitgestellten Angriffsvertrag an, wie unten gezeigt:



Um nun die Angriffsfunktion aufzurufen, müssen Sie einen Wert von 1 Ether oder mehr einzahlen. In diesem Fall haben wir also 2 Ether ausgewählt, wie unten gezeigt:


Zahlen Sie zuerst 2 ETH ein!


Klicken Sie anschließend auf „Angreifen“. Sie werden feststellen, dass die 2 Ether, die Sie eingezahlt haben, sofort abgehoben und dem Attack-Vertrag hinzugefügt werden, wie aus dem untenstehenden Saldo von 2 Ether hervorgeht:


2 ETH wurden an den Attack Smart Contract übertragen


Das darf natürlich nicht passieren, denn die Langzeitsperre soll bereits mit der Einzahlung in Kraft treten. Wie wir wissen, reduziert die Anweisung type(uint).max+1-timeLock.locktime(address(this)) natürlich die Sperrzeit durch Verwendung der Funktion raiseLockTime. Genau aus diesem Grund können wir das Ether-Guthaben sofort abheben.


Was uns zu der offensichtlichen Frage bringt: Gibt es Möglichkeiten, die Schwachstelle durch Ganzzahlüberlauf und -unterlauf zu beheben?

2 Möglichkeiten, die Schwachstelle durch Ganzzahlüberlauf/-unterlauf zu umgehen

Da wir erkannt haben, dass die Schwachstelle durch Ganzzahlüberlauf/-unterlauf verheerende Folgen haben kann, wurden einige Korrekturen für diesen Fehler eingeführt. Schauen wir uns beide Korrekturen an und wie sie einen solchen Fehler umgehen:


Methode 1: Verwenden Sie die SafeMath-Bibliothek von OpenZeppelin

Open Zeppelin bietet als Organisation viel, wenn es um Cybersicherheitstechnologie und -dienste geht, wobei die SafeMath-Bibliothek Teil seines Repositorys für die Entwicklung intelligenter Verträge ist. Dieses Repo enthält Verträge, die in Ihren Smart-Contract-Code importiert werden können, darunter auch die SafeMath-Bibliothek.


Sehen wir uns an, wie eine der Funktionen in SafeMath.sol auf Ganzzahlüberlauf prüft:


Versuchen Sie esFügen Sie eine SafeMath-Funktion hinzu


Nachdem nun die Berechnung von a+b erfolgt ist, erfolgt eine Prüfung, ob c<a erfolgt. Dies gilt natürlich nur im Fall eines Integer-Überlaufs.


Da die Compilerversion von Solidity 0.8.0 und höher erreicht, sind nun Überprüfungen auf Ganzzahlüberlauf und -unterlauf integriert. Daher kann man diese Bibliothek weiterhin verwenden, um nach dieser Schwachstelle zu suchen, sowohl wenn man die Sprache als auch diese Bibliothek verwendet. Wenn Ihr Smart Contract eine Compilerversion kleiner als 0.8.+ erfordert, müssen Sie natürlich diese Bibliothek verwenden, um einen Über- oder Unterlauf zu vermeiden.


Methode 2: Verwenden Sie die Version 0.8.0 des Compilers

Wenn Sie nun, wie bereits erwähnt, für Ihren Smart Contract eine Compilerversion ab 0.8.0 verwenden, verfügt diese Version über einen integrierten Prüfer für eine solche Schwachstelle.


Um tatsächlich zu überprüfen, ob es mit dem oben genannten Smart-Vertrag funktioniert, wird beim Ändern der Compiler-Version auf „^0.8.0“ und beim erneuten Bereitstellen der folgende „Zurücksetzen“-Fehler angezeigt:


Fehler zurücksetzen, der einen Ganzzahlüberlauf verhindert


Selbstverständlich erfolgt keine Einzahlung der 2 Ether, was an der Kontrolle auf den Überlauf des Zeitsperrwertes liegt. Daher ist keine Auszahlung möglich, da überhaupt kein Geld eingezahlt wurde.


Die Übertragung von 2 ETH in den Attack-Vertrag wurde verhindert


Ohne Zweifel hat der Funktionsaufruf Attack.attack() hier nicht funktioniert, also ist alles gut!

Zusammenfassung der Überlauf-/Unterlauf-Sicherheitslücke

Wenn Sie aus diesem langen Blog-Beitrag etwas lernen sollten, dann ist es, dass das Ignorieren dieser Schwachstelle, wie beim BEC-Angriff, sich als kostspielig erweisen kann. Wie Sie auch sehen können, kann es leicht zu nicht böswilligen Fehlern kommen, wenn diese Option nicht aktiviert wird. Oder es ist genauso einfach für Hacker, diese Sicherheitslücke auszunutzen.


Apropos und basierend auf unserem Verständnis, wie der BEC-Angriff stattgefunden hat, kann das Erkennen dieser Schwachstelle dank der angebotenen Korrekturen viel dazu beitragen, Angriffe beim Schreiben Ihrer Smart Contracts zu verhindern. Auch wenn es noch mehrere andere Schwachstellen bei Smart Contracts gibt, die darauf warten, Sie zum Stolpern zu bringen.