Smart Contract – Sicherheit
Letzte Änderung: , Invalid DateTime
Ethereum-Smart-Contracts sind extrem flexibel und in der Lage, sowohl große Mengen an Token (oft über $1B) zu halten, als auch unveränderliche Logik auf der Grundlage von zuvor eingesetztem Smart-Contract-Code auszuführen. Während das ein lebendiges und kreatives Ökosystem vernetzter Smart Contracts geschaffen hat, das ohne Vertrauen auskommt, ist es auch das perfekte Ökosystem, um Angreifer anzulocken, die davon profitieren, Schwachstellen in Smart Contracts und unerwartetem Verhalten in Ethereum auszunutzen. Code in Smart Contracts kann normalerweise nicht geändert werden, um Sicherheitslücken zu schließen. Assets die aus Smart Contracts gestohlen wurden, können nicht zurück geholt werden und gestohlene Assets sind sehr schwer nachzuverfolgen. Die Gesamtsumme gestohlener oder verlorener Werte infolge von Problemen mit Smart Contracts beläuft sich auf weit über 1 Milliarde Dollar. Einige der größeren Codefehlern in Smart Contracts beinhalten:
- Parity Multi-Sig-Problem Nr. 1: 30 Millionen US-Dollar verloren(opens in a new tab)
- Parity Multi-Sig-Problem Nr. 2: 300 Millionen US-Dollar verschlossen (niemand hat Zugriff)(opens in a new tab)
- TheDAO-Hack, 3,6 Millionen ETH! Über 1 Milliarde US-Dollar in heutigen ETH-Werten(opens in a new tab)
Voraussetzungen
Im Folgenden wird die Sicherheit von Smart Contracts erläutert. Als Voraussetzung sollten Sie daher mit Smart Contracts vertraut sein oder sich vertraut machen, bevor Sie sich dem Thema Sicherheit widmen.
So schreiben Sie einen sicheren Smart-Contract-Code
Bevor Sie Code im Ethereum-Mainnet einführen, ist es wichtig, ausreichende Vorsichtsmaßnahmen zu treffen, damit alle Vorgänge geschützt sind, die Werte betreffen und über den Smart Contract durchgeführt werden. In diesem Artikel werden wir einige spezielle Angriffe diskutieren, Ressourcen bereitstellen, um mehr über Angriffstypen zu erfahren. Außerdem erfahren Sie mehr über einige grundlegende Werkzeuge und Best Practices, um sicherzustellen, dass Ihre Verträge korrekt und sicher funktionieren.
Audits sind keine Garantie
Vor Jahren waren die Tools zum Schreiben, Kompilieren, Testen und Bereitstellen von Smart Contracts noch unausgereift. Das führte dazu, dass viele Projekte willkürlichen Solidity-Code schreiben konnten und ihn an einen Auditor übergaben, der den Code auf seine klare Funktionsweise und deren Sicherheit untersuchte. Im Jahr 2020 sind die Entwicklungsprozesse und Tools, die das Programmieren in Solidity unterstützen, wesentlich besser. Wenn Sie die Best Practices umsetzen, stellen Sie damit sicher, dass Ihr Projekt leichter zu verwalten ist, und erfüllen viele Sicherheitsaspekte für Ihr Projekt. Den Smart Contract am Ende einem Audit zu unterziehen, reicht als einziger Sicherheitsaspekt für Ihr Projekt nicht mehr aus. Die Sicherheit beginnt vor dem Schreiben der ersten Zeile des Smart-Contract-Codes, und zwar mit geeigneten Design- und Entwicklungsprozessen.
Prozess der Entwicklung von Smart Contracts
Mindestvoraussetzungen:
- Der gesammte Code wird in einem Versionskontrollsystem gespeichert, wie z. B. Git.
- Alle Codeänderungen erfolgen über Pull-Anforderungen.
- Für alle Pull-Anforderungen gibt es mindestens einen Prüfer. Wenn Sie alleine an einem Solo-Projekt arbeiten, sollten Sie überlegen, einen weiteren Solo-Autor zu finden und Codeprüfungen gegenseitig durchzuführen.
- Ein einziger Befehl kompiliert, verteilt und führt eine Reihe von Tests für Ihren Code aus, und zwar mithilfe einer Ethereum-Entwicklungsumgebung (siehe: Truffle).
- Sie haben Ihren Code über grundlegende Codeanalysetools wie Mythril und Slither ausgeführt, idealerweise bevor die einzlenen Pull-Anforderungen zusammengeführt werden, um so Unterschiede in der Ausgabe zu vergleichen.
- Solidity zeigt KEINE Compiler-Warnungen an.
- Ihr Code ist gut dokumentiert.
Es gibt noch viele weitere Aspekte des Entwicklungsprozesses, die erläutert werden könnten, doch diese Liste ist ein guter Ausgangspunkt. Weitere Informationen und ausführliche Erklärungen finden Sie in der Checkliste für Prozessqualität von DeFiSafety(opens in a new tab). DefiSafety(opens in a new tab) ist eine inoffizielle Publikation von verschiedenen großen und öffentlichen Ethereum-dApps. Im Rahmen des DeFiSafety-Bewertungssystems wird überprüft, wie gut sich das Projekt an diese Prozessqualitätsprüfungsliste hält. Dabei werden folgende Prozesse eingehalten:
- Sie erzeugen sichereren Code durch reproduzierbare, automatisierte Tests.
- Auditoren können Ihr Projekt effektiver prüfen.
- Neue Entwickler können einfacher einsteigen.
- Bietet Entwicklern die Möglichkeit, schnell zu iterieren, testen und Feedback zu Änderungen zu erhalten.
- So kommt es wahrscheinlich zu weniger Rückschritten in Ihrem Projekt.
Angriffe und Schwachstellen
Da Sie nun Solidity-Code unter Beachtung eines effizienten Entwicklungsprozesses schreiben, sollten Sie sich mit einigen gängigen Schwachstellen vertraut machen, um ein Bild davon zu bekommen, was schief gehen kann.
Re-entrancy (Wiedereintritt)
Re-entrancy ist eines der größten und bedeutendsten Sicherheitsprobleme, die bei der Entwicklung von Smart Contracts zu berücksichtigen sind. Da die EVM nicht mehrere Contracts gleichzeitig abwickeln kann, pausiert ein Vertrag, der einen anderen Contract aufruft, die Ausführung des Vertragsaufrufs und den Speicherzustand, bis der Aufruf zurückkehrt. An diesem Punkt läuft die Ausführung normal weiter. Durch dieses Pausieren und Neustarten kann eine Schwachstelle entstehen, die als "Re-entrancy" bekannt ist.
Im Folgenden sehen Sie eine einfache Vertragsversion, der anfällig für eine Re-entrancy ist:
1// DIESER CONTRACT ENTHÄLT ABSICHTLICHE VERWUNDBARKEITEN, NICHT VERWENDEN!2contract Victim {3 mapping (address => uint256) public balances;45 function deposit() external payable {6 balances[msg.sender] += msg.value;7 }89 function withdraw() external {10 uint256 amount = balances[msg.sender];11 (bool success, ) = msg.sender.call.value(amount)("");12 require(success);13 balances[msg.sender] = 0;14 }15}16Alles anzeigenKopieren
Um es einem Benutzer zu ermöglichen, ETH auszuzahlen, sind folgende Funktionen im Smart Contract gespeichert:
- Lesen, wie viel Guthaben ein Benutzer hat
- Dem Nutzer diesen Betrag in ETH senden
- Das Guthaben auf 0 zurücksetzen, so dass das Guthaben des Nutzers nicht doppelt ausgezahlt werden kann
Bei einem Aufruf von einem regulären Konto (z. B. Ihrem eigenen MetaMask-Konto) funktioniert dies wie erwartet: msg.sender.call.value() sendet Ihrem Konto einfach ETH. Allerdings können Smart Contracts auch Aufrufe tätigen. Wenn ein benutzerdefinierter bösartiger Vertrag die Funktion withdraw()
aufruft, sendet msg.sender.call.value() nicht nur amount
von ETH, sondern ruft auch implizit den Vertrag auf, um mit der Codeausführung zu beginnen. Stellen Sie sich diesen böswilligen Vertrag vor:
1contract Attacker {2 function beginAttack() external payable {3 Victim(VICTIM_ADDRESS).deposit.value(1 ether)();4 Victim(VICTIM_ADDRESS).withdraw();5 }67 function() external payable {8 if (gasleft() > 40000) {9 Victim(VICTIM_ADDRESS).withdraw();10 }11 }12}13Alles anzeigenKopieren
Der Aufruf von Attacker.beginAttack() startet einen Zyklus, der ähnlich aussieht:
10.) EOA-Aufrufe des Attacker.beginAttack() mit 1 ETH20.) Attacker.beginAttack() hinterlegt 1 ETH in Opferaccount34 1.) Angreifer -> Victim.withdraw()5 1.) Victim reads balances[msg.sender]6 1.) Opfer sendet ETH an den Angreifer (welcher die Standardfunktion ausführt)7 2.) Angreifer -> Victim.withdraw()8 2.) Victim reads balances[msg.sender]9 2.) Opfer sendet ETH an den Angreifer (welcher die Standardfunktion ausführt)10 3.) Angreifer -> Victim.withdraw()11 3.) Victim reads balances[msg.sender]12 3.) Opfer sendet ETH an den Angreifer (welcher die Standardfunktion ausführt)13 4.) Angreifer hat nicht mehr genug Gas, kehrt zurück ohne erneut aufzurufen14 3. saldo[msg.sender] = 0;15 2. Salden[msg.sender] = 0; (es war bereits 0)16 1.) Salden[msg.sender] = 0; (es war bereits 0)17Alles anzeigen
Über den Aufruf von Attacker.beginAttack mit 1 ETH erfolgt ein Re-entrancy-Angriff auf das Opfer. Dadurch wird mehr ETH ausgezahlt, als in den Vertrag gegeben wurde (aus den Salden anderer Benutzer, was bewirkt, dass der Smart Contract des Ofers unterbesichert (under-collaterized) wird).
So umgehen Sie Re-entrancy (der falsche Weg)
Sie könnten in Erwägung ziehen, die Re-entrancy zu unterbinden, indem Sie einfach verhindern, dass Smart Contracts mit Ihrem Code interagieren. Sie suchen stackoverflow und finden dieses Code-Snippet mit Tonnen von Upvotes:
1function isContract(address addr) internal returns (bool) {2 uint size;3 assembly { size := extcodesize(addr) }4 return size > 0;5}6Kopieren
Das scheint sinnvoll: Vertäge haben Code und wenn der Anrufer Code hat, erlauben Sie ihm keine Einzahlung. Fügen wir diesen Aspekt also hinzu:
1// DIESER CONTRACT ENTHÄLT ABSICHTLICHE VERWUNDBARKEITEN, NICHT VERWENDEN!2contract ContractCheckVictim {3 mapping (address => uint256) public balances;45 function isContract(address addr) internal returns (bool) {6 uint size;7 assembly { size := extcodesize(addr) }8 return size > 0;9 }1011 function deposit() external payable {12 require(!isContract(msg.sender)); // <- NEW LINE13 balances[msg.sender] += msg.value;14 }1516 function withdraw() external {17 uint256 amount = balances[msg.sender];18 (bool success, ) = msg.sender.call.value(amount)("");19 require(success);20 balances[msg.sender] = 0;21 }22}23Alles anzeigenKopieren
Wenn nun ETH eingezahlt werden soll, darf Ihre Adresse keinen Smart-Contract-Code enthalten. Das kann allerdings leicht mit dem folgenden Angreifervertrag ausgehebelt werden:
1contract ContractCheckAttacker {2 constructor() public payable {3 ContractCheckVictim(VICTIM_ADDRESS).deposit(1 ether); // <- New line4 }56 function beginAttack() external payable {7 ContractCheckVictim(VICTIM_ADDRESS).withdraw();8 }910 function() external payable {11 if (gasleft() > 40000) {12 Victim(VICTIM_ADDRESS).withdraw();13 }14 }15}16Alles anzeigenKopieren
Während sich der erste Angriff auf die Vertragslogik bezogen hat, liegt nun ein Angriff auf das Verhalten bei der Bereitstellung eines Ethereum-Vertrags vor. Bei der der Erstellung hat ein Vertrag seinen Code noch nicht zurückgegeben, der an seiner Adresse eingesetzt werden soll, doch er behält die volle EVM-Kontrolle WÄHREND dieses Prozesses.
Technisch kann verhindert werden, dass Smart Contracts Ihren Code aufrufen. Verwenden Sie dafür folgende Zeile:
1require(tx.origin == msg.sender)2Kopieren
Allerdings ist das noch immer keine gute Lösung. Einer der spannendsten Aspekte von Ethereum sind die Gestaltungsmöglichkeiten, wie sich Smart Contracts miteinander integrieren und aufeinander aufbauen. Wenn Sie die obige Zeile verwenden, beschränken Sie damit den Nutzen Ihres Projekts.
So umgehen Sie Re-entrancy (der richtige Weg)
Wenn Sie ganz einfach die Reihenfolge der Speicheraktualisierung und des externen Aufrufs ändern, verhindern Sie die Re-entrancy, die den Angriff ermöglichte. Ein Rückruf in die Auszahlung wird dem Angreifer zwar möglich, doch das ist nutzlos, da die balances
bereits auf 0 gesetzt sind.
1contract NoLongerAVictim {2 function withdraw() external {3 uint256 amount = balances[msg.sender];4 balances[msg.sender] = 0;5 (bool success, ) = msg.sender.call.value(amount)("");6 require(success);7 }8}9Kopieren
Der obige Code folgt dem Designmuster "Prüfungen-Auswirkungen-Interaktionen", das zum Schutz vor Re-entrancy beiträgt. Hier erfahren Sie mehr über Prüfungen-Auswirkungen-Interaktionen(opens in a new tab).
So umgehen Sie Re-entrancy (die nukleare Option)
Jedes Mal, wenn Sie ETH an eine nicht vertrauenswürdige Adresse senden oder mit einem unbekannten Vertrag interagieren (wie beispielsweise das Aufrufen von transfer()
einer von dir angegebenen Token-Adresse), eröffnet sich für Sie die Möglichkeit der RE-entrancy. Durch die Gestaltung von Smart Contracts, die weder ETH senden noch nicht vertrauenswürdige Verträge aufrufen, verhindern Sie die Möglichkeit der Re-entrancy.
Weitere Angriffstypen
Die obigen Angriffstypen beziehen sich auf Probleme mit Smart-Contract-Code (Re-entrancy) und Ethereum-Kuriositäten (Codeausführung innerhalb der Vertragskonstruktoren, bevor Code unter der Vertragsadresse verfügbar ist). Doch es gibt viele weitere Angriffstypen, sie Sie kennen sollten:
- Front-running (Vorweglaufen)
- ETH-Sendungsablehnung
- Ganzzahlüberlauf/-unterlauf
Weiterführende Informationen:
- Consensys – Bekannte Smart-Contract-Angriffe(opens in a new tab) – Eine sehr gut lesbare Erklärung der wichtigsten Schwachstellen, mit Beispielcode für die meisten.
- SWC Registry(opens in a new tab) – Kuratierte Liste der CWEs, die für Ethereum und Smart Contracts gelten
Sicherheitstools
Es ist unumgänglich, dass Sie die Sicherheitsgrundlagen von Ethereum verstehen und mit einem professionellen Prüfer zusammenarbeiten. Doch für die Überprüfung Ihres Codes gibt es viele Tools, die helfen, mögliche Probleme in Ihrem Code zu beleuchten.
Smart Contract – Sicherheit
Slither – In Python 3 geschriebener statischer Analyserahmen für Solidity
MythX – Sicherheitsanalyse-API für Ethereum-Smart Contracts
Mythril – Sicherheitsanalyse für EVM-Bytecode
Manticore – Eine Befehlszeilenschnittstelle, die ein symbolisches Ausführungswerkzeug für Smart Contracts und Binärdaten einsetzt
Securify – Sicherheitsscanner für Ethereum-Smart Contracts
ERC20 Verifier – Ein Verifizierungstool zur Prüfung der Übereinstimmung eines Vertrags mit dem ERC20-Standard
Formale Verifizierung
Informationen zur formalen Verifizierung
- How formal verification of smart-contacts works(opens in a new tab) 20. Juli 2018 – Brian Marick
- How Formal Verification Can Ensure Flawless Smart Contracts(opens in a new tab) 20. Januar 2018 – Bernard Mueller
Tools einsetzen
Zwei der beliebtesten Tools für eine Smart-Contract-Sicherheitsanalyse sind:
- Slither(opens in a new tab) by Trail of Bits(opens in a new tab) (hosted version: Crytic(opens in a new tab))
- Mythril(opens in a new tab) von ConsenSys(opens in a new tab) (gehostete Version: MythX(opens in a new tab))
Beide Tools sind nützlich, um Ihren Code zu analysieren und Fehler zu melden. Für jedes Tool gibt es eine [commercial] gehostete Version, sie sind aber auch für den lokalen Betrieb verfügbar. Das folgende Beispiel ist ein schnelles Beispiel für die Ausführung von Slither, das in einem praktischen Docker-Image trailofbits/eth-security-toolbox
zur Verfügung gestellt wird. Sofern noch nicht geschehen, sollten Sie Node.js(opens in a new tab) installieren.
$ mkdir test-slither$ curl https://gist.githubusercontent.com/epheph/460e6ff4f02c4ac582794a41e1f103bf/raw/9e761af793d4414c39370f063a46a3f71686b579/gistfile1.txt > bad-contract.sol$ docker run -v `pwd`:/share -it --rm trailofbits/eth-security-toolboxdocker$ cd /sharedocker$ solc-select 0.5.11docker$ slither bad-contract.sol
Folgende Ausgabe wird erstellt:
ethsec@1435b241ca60:/share$ slither bad-contract.solINFO:Detectors:Reentrancy in Victim.withdraw() (bad-contract.sol#11-16):External calls:- (success) = msg.sender.call.value(amount)() (bad-contract.sol#13)State variables written after the call(s):- balances[msg.sender] = 0 (bad-contract.sol#15)Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilitiesINFO:Detectors:Low level call in Victim.withdraw() (bad-contract.sol#11-16):- (success) = msg.sender.call.value(amount)() (bad-contract.sol#13)Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#low-level-callsINFO:Slither:bad-contract.sol analyzed (1 contracts with 46 detectors), 2 result(s) foundINFO:Slither:Use https://crytic.io/ to get access to additional detectors and GitHub integrationAlles anzeigen
Slither hat hier das Potenzial für eine Re-entrancy erkannt, identifiziert die Schlüsselzeilen, in denen das Problem auftreten könnte, und stellt einen Link für weitere Informationen zum Problem bereit:
So werden Sie schnell über mögliche Probleme mit Ihrem Code informiert. Wie alle automatisierten Testwerkzeuge ist Slither nicht perfekt und es irrt sich manchmal, indem es zu viel rückmeldet. Es kann vor einer möglichen Re-entrancy warnen, selbst wenn keine ausbeutbare Schwachstelle vorhanden ist. Oft ist die Überprüfung des UNTERSCHIEDS in der Slither-Ausgabe zwischen Codeänderungen extrem aufschlussreich, denn es hilft bei der Entdeckung von Schwachstellen, die viel früher eingeführt wurden, anstatt zu warten, bis Ihr Projektcode vollständig ist.
Weiterführende Informationen
Leitfaden für Best Practices zur Sicherheit von Smart Contracts
- consensys.github.io/smart-contract-best-practices/(opens in a new tab)
- GitHub(opens in a new tab)
- Sammlung von Sicherheitsempfehlungen und Best Practices(opens in a new tab)
Standard für die Sicherheitsüberprüfung von Smart Contracts (SCSVS)
Kennen Sie eine Community-Ressource, die Ihnen geholfen hat? Bearbeiten Sie diese Seite und fügen Sie sie hinzu.
Verwandte Tutorials
- Sicherer Entwicklungs-Workflow
- So verwenden Sie Slither, um Fehler in Smart Contracts zu finden
- So finden Sie mit Manticore Fehler in Smart Contract
- Sicherheitsrichtlinien
- Token-Sicherheit