Einführung
Unser Unternehmen befasst sich mit der Sicherheitsüberprüfung intelligenter Verträge, und das Problem der Verwendung automatisierter Tools ist sehr akut. Wie viel können sie helfen, verdächtige Orte zu identifizieren, welche sollten verwendet werden, was können sie tun und was sind die Besonderheiten der Arbeit in diesem Bereich? Diese und verwandte Themen sind Gegenstand dieses Artikels. Und das Material wird Versuche sein, mit Hilfe der interessantesten Vertreter und Rezepte für die Einführung dieser äußerst bunten und äußerst interessanten Software mit echten Verträgen zu arbeiten. Zuerst wollte ich einen Artikel machen, aber nach einiger Zeit wurde die Informationsmenge zu groß, und so wurde beschlossen, eine Reihe von Artikeln zu erstellen, einen für jeden Autoanalysator. Die Liste, aus der wir die Werkzeuge entnehmen, wird zum Beispiel hier vorgestellt . Wenn jedoch beim Schreiben andere interessante Werkzeuge auftauchen, werde ich sie gerne beschreiben und testen.
Ich muss sagen, dass die Prüfungsaufgaben äußerst interessant waren, weil Bisher haben Entwickler den wirtschaftlichen Aspekten von Algorithmen und der internen Optimierung nicht viel Aufmerksamkeit geschenkt. Bei der Prüfung intelligenter Verträge wurden mehrere interessante Angriffsmethoden hinzugefügt, die bei der Suche nach Fehlern berücksichtigt werden müssen. Wie sich herausstellte, erschienen auch viele Tools für automatische Tests: statische Analysatoren, Bytecode-Analysatoren, Fuzzers, Parser und viele andere gute Software.
Der Zweck des Artikels: die Verbreitung von sicherem Vertragscode zu fördern und Entwicklern zu ermöglichen, dumme Fehler, die oft am ärgerlichsten sind, schnell und einfach zu beseitigen. Wenn das Protokoll selbst absolut zuverlässig ist und ein ernstes Problem löst, kann das Vorhandensein eines dummen Fehlers, der in der Testphase vergessen wurde, die Lebensdauer des Projekts ernsthaft ruinieren. Lassen Sie uns daher lernen, mindestens Werkzeuge zu verwenden, mit denen „wenig Blut“ bekannte Probleme beseitigen kann.
Mit Blick auf die Zukunft muss ich sagen, dass die häufigsten kritischen Fehler, auf die wir bei Audits gestoßen sind, immer noch logische Implementierungsprobleme sind und keine typischen Schwachstellen wie Zugriffsrechte, ganzzahliger Überlauf oder Wiedereintritt. Eine umfassende Prüfung von Lösungen ist ohne erfahrene Entwickler nicht möglich, die in der Lage sind, die Logik von Verträgen auf hoher Ebene, ihren Lebenszyklus, Aspekte des tatsächlichen Betriebs und der Einhaltung der Aufgabe sowie nicht nur typische Angriffsmuster zu prüfen. Es ist eine Logik auf hoher Ebene, die häufig zu kritischen Fehlern führt.
Aber Warnungen, typische Löcher und Fehler, die aus Unachtsamkeit herausgelassen wurden und nicht übersehen werden sollten, sind das Schicksal automatischer Analysegeräte. Sie sollten diese Aufgaben besser bewältigen als Menschen. Es ist diese These, die getestet wird.
Merkmale der intelligenten Prüfung des Vertragscodes
Die intelligente Prüfung von Vertragscodes ist ein eher spezifischer Bereich. Trotz seiner geringen Größe ist der Ethereum Smart Contract ein vollwertiges Programm, das komplexe Zweige, Schleifen und Entscheidungsbäume organisieren und sogar scheinbar einfache Transaktionen automatisieren kann, bei denen bei jedem Schritt alle möglichen Zweige durchdacht werden müssen. Unter diesem Gesichtspunkt ist die Blockchain-Entwicklung äußerst einfach, sehr ressourcenintensiv und erinnert stark an die Entwicklung von System- und eingebetteter Software in C / C ++ - und Assembler-Sprachen. Aus diesem Grund freuen wir uns, in Interviews die Entwickler von Low-Level-Algorithmen, den Netzwerkstapel, hoch ausgelastete Dienste und alle zu sehen, die sich mit Low-Level-Optimierung und Code-Auditing befasst haben.
Aus Sicht des Entwicklers ist Solidity auch ziemlich spezifisch, obwohl es von fast jedem Programmierer und in den ersten Schritten leicht zu lesen ist und äußerst einfach erscheint. Solidity-Code ist ziemlich einfach zu lesen und jedem Entwickler bekannt, der sich mit C / C ++ - Syntax und OOP wie JavaScript auskennt.
Hier ist die Einfachheit des Codes der Schlüssel zum Überleben, nichts Schweres funktioniert, daher wird das gesamte Arsenal der Entwicklung auf niedriger Ebene in der Arbeit verwendet - Algorithmen, die eine effiziente Nutzung von Ressourcen ermöglichen, Speicherplatz sparen: Merkle-Bäume, Bloom-Filter, „faules“ Laden von Ressourcen, Abrollen von Schleifen, manuelle Speicherbereinigung und vieles mehr.
Eine kleine Menge Quellcode und daraus resultierenden Bytecode.
Ein separater Smart-Vertrag ist in Bezug auf das Bytecode-Volumen begrenzt, jedes Byte kostet eine bestimmte Menge Gas und das Maximum ist von oben begrenzt, sodass Sie (im Moment) etwa 10 KB in die Blockchain schieben können. Es funktioniert nicht mehr. Hier ist ein guter Artikel darüber, wie viel ein Vertrag kostet und wie viel Gas kostet . Daher kann nicht viel geschoben werden. Wenn Sie übertreiben, sind mehrere tausend Zeilen „durchschnittlicher“ Code das Maximum. Mehrere Dutzend Methoden, die fehlende Aggregation und die allgemein komplexe Logik sind äußerst charakteristisch für Verträge. Für alles, was nicht passt, müssen Sie den Code in separaten Bibliotheken auswählen, um das Verfahren zum Hochladen in das Netzwerk zu ändern und zu verkomplizieren. Solidity-Entwickler können gerne eine Menge Code in einen Vertrag einbinden, müssen jedoch einfach ihre Vertragssysteme korrekt anordnen, indem sie separate Klassenbibliotheken mit eigenem Speicher erstellen. Und es ist praktisch, solche separaten "Klassen" in separate Dateien zu zerlegen. Daher ist das Lesen des Code der Verträge ziemlich gut, alles ist von Anfang an gut strukturiert - sonst funktioniert es einfach nicht. Als Beispiel empfehle ich , sich anzusehen, wie ERC721 in Openzeppelin-Solidität hergestellt wird .
Gas, Gas, Gas
Gas führt eine zusätzliche Logikschicht in die Ausführung des Vertragscodes ein, die eine Prüfung erfordert. Darüber hinaus kann im Gegensatz zum herkömmlichen Code derselbe Codeabschnitt unterschiedliche Gasmengen verbrauchen. Eine Tabelle mit EVM-Opcodes und ihren Kosten ist hilfreich, um die Gasbeschränkungen zu verstehen. Hier ist sie .
Um zu demonstrieren, warum Sie viel Zeit für die Bewertung von Gas aufwenden müssen, betrachten Sie diesen Pseudocode (natürlich unrealistisch; es ist eine schlechte Idee, in der Schleife mit Äther zu feuern):
// function fixSomeAccountAction(uint _actionId) public onlyValidator { // … events[msg.sender].push(_actionId); } // , function receivePaymentForSavedActions() { // ... for (uint256 i = 0; i < events[msg.sender].length; i++) { // actionId uint actionId = events[msg.sender][i]; // action uint payment = getPriceByEventId(actionId); if (payment > 0) { paymentAccumulators[msg.sender] += payment; } emit LogEventPaymentForAction(msg.sender, actionId, payment); // … // delete “events[msg.sender][i]” from array } }
Tatsache ist, dass der Zyklus im Vertrag Ereignisse [msg.sender] .length mal ausgeführt wird und jede Iteration ein Eintrag in der Blockchain ist (transfer () und emit ()). Wenn die Länge des Arrays klein ist, erfüllt der Zyklus sein Zehnfaches und verteilt die Zahlung für jede Aktion. Wenn das Ereignis-Array [msg.sender] jedoch groß ist, gibt es viele Iterationen und das verbrauchte Gas läuft in die fest codierte maximale Gasgrenze (~ 8.000.000). Die Transaktion wird abgebrochen und funktioniert jetzt nie mehr, da es keine Möglichkeit gibt, die Länge des Ereignis-Arrays [msg.sender] im Vertrag zu verringern. Wenn der Zyklus nicht nur einen Einheitswert berechnet, sondern in die Blockchain schreibt (z. B. werden einige Gebühren gezahlt, Zahlungen für Aktionen), ist die zulässige Anzahl von Iterationen erheblich begrenzt. Überzeugen Sie sich selbst - Limit: 8.000.000, Aufzeichnung eines neuen 256-Bit-Werts: 20.000. Sie können Metadaten nur für ein paar Hundert von 256-Bit-Adressen mit einigen Metadaten speichern oder aktualisieren. Ein weiterer unterhaltsamer Teil ist das Schreiben eines neuen Werts: 20.000 und eines Updates eines vorhandenen Werts: 5.000, also auch mit genau der gleichen Umgebung Ihres Vertrags, wenn Sie eine Übertragung durchführen Token an eine Adresse, die bereits Token enthält, geben Sie viermal weniger Benzin (5.000 gegenüber 20.000) für einen Datensatz aus.
Seien Sie daher nicht überrascht, dass das Thema Gas in intelligenten Verträgen so eng mit der Sicherheit von Verträgen zusammenhängt, da sich die Situation, in der Gelder aus praktischer Sicht dauerhaft im Vertrag stecken, kaum von der Situation unterscheidet, in der sie gestohlen wurden. Die Tatsache, dass der ADD-Befehl 3 Gas und SSTORE (Einsparung in den Speicher) kostet: 20 000, bedeutet, dass die teuerste Ressource in der Blockchain die Speicherung ist, und die Aufgaben der Optimierung des Vertragscodes haben viel mit den Aufgaben der Entwicklung auf niedriger Ebene in C und ASM für Embedded gemeinsam Systeme, bei denen Speicher ebenfalls eine sehr begrenzte Ressource ist.
Schöne Blockchain
Dies ist ein sehr positiver Absatz darüber, warum die Blockchain aus Sicherheitsgründen nur für den Prüfer so gut ist. Der Determinismus der Ausführung des Vertragscodes ist der Schlüssel zum erfolgreichen Debuggen und Wiedergeben von Fehlern und Schwachstellen. Technisch gesehen kann jeder Aufruf des Vertragscodes auf jeder Plattform mit einer gewissen Genauigkeit reproduziert werden. Dadurch können die Tests überall ausgeführt werden und sind äußerst einfach zu unterstützen. Die Untersuchung von Vorfällen ist zuverlässig und unbestreitbar. Jetzt wissen wir immer, wer wann welche Funktion aufgerufen wurde, mit welchen Parametern, welcher Code sie verarbeitete und was das Ergebnis war. All dies ist vollständig bestimmt, d.h. spielt überall, auch in JS auf einer Webseite. Wenn wir über Ethereum sprechen, ist jeder Testfall extrem einfach in praktischem JavaScript zu schreiben, einschließlich Fuzzing-Parametern, und funktioniert überall dort, wo es Node.js gibt.
All diese schönen Wörter sollten die Entwickler jedoch nicht entspannen, da, wie oben erwähnt, die schwerwiegendsten Fehler logisch sind und für sie der Determinismus der Ausführung eine orthogonale Eigenschaft ist.
Die Umgebung für die Montage des Vertrages
Um den Artikel zu schreiben, habe ich einen alten experimentellen Vertrag für die Buchung eines Hauses vom Smartz-Designer abgeschlossen: https://github.com/smartzplatform/constructor-eth-booking . Mit dem Vertrag können Sie eine Aufzeichnung des Objekts (Apartment oder Hotelzimmer) erstellen, den Preis und die Liefertermine festlegen. Danach wartet der Vertrag auf die Zahlung und legt bei Erhalt den Buchungsvorgang fest, wobei das Guthaben auf dem Restbetrag verbleibt, bis der Gast das Zimmer betritt und wird die Eingabe nicht bestätigen. Zu diesem Zeitpunkt erhält der Eigentümer des Zimmers die Zahlung. Der Vertrag ist im Wesentlichen eine Zustandsmaschine, deren Zustände und Übergänge in Booking.sol eingesehen werden können. Wir haben es ziemlich schnell gemacht, es während des Entwicklungsprozesses geändert und es nicht geschafft, eine große Anzahl von Tests durchzuführen. Es ist weit entfernt von einer neuen Version des Compilers und mehr oder weniger umfangreicher interner Logik. Schauen wir uns also an, wie die Analysatoren damit umgehen, welche Fehler sie finden, und fügen wir bei Bedarf unsere eigenen hinzu.
Arbeiten Sie mit verschiedenen Versionen von solc
Verschiedene Analysatoren müssen auf unterschiedliche Weise verwendet werden - einige werden vom Docker aus gestartet, andere verwenden vorgefertigten kompilierten Bytecode, und der Prüfer selbst muss sich nicht mit einem Paar befassen, sondern mit Dutzenden früher Verträge mit verschiedenen Versionen des Compilers. Daher müssen Sie in der Lage sein, verschiedene Versionen von solc
sowohl im Host-System als auch im Docker-Image und im Trüffel unterschiedlich zu " solc
", damit ich Ihnen diese wenigen schmutzigen Hack-Optionen geben kann:
1 Weg: Innentrüffel
Dafür werden keine Tricks benötigt, weil Ab Trüffelversion 5.0.0 können Sie die Compilerversion wie in diesem Diff direkt in truffle.js angeben.
Jetzt lädt truffle den erforderlichen Compiler herunter und führt ihn aus. Vielen Dank an das Team dafür, Solidity ist eine junge Sprache, es gibt gravierende Änderungen in der Sprache und der Wechsel von Version zu Version für den Auditor ist inakzeptabel. Auf diese Weise können Sie neue Fehler einführen und alte maskieren.
Methode 2: Ersetzen von / usr / bin / solc im Docker-Container des Analysators
Wenn der Analysator in Form einer Docker-Datei verteilt ist, können Sie ihn beim Zusammenstellen eines Docker-Images ersetzen, indem Sie der Docker-Datei eine Zeile hinzufügen, die die gewünschte Version solc
direkt aus dem Image erhält, die sie aus dem Netzwerk abruft und / usr / bin / solc ersetzt:
COPY --from=ethereum/solc:0.4.19 /usr/bin/solc /usr/bin
3 Wege: Ersetzen von / usr / bin / solc
Der schmutzigste Weg in der Stirn, wenn es überhaupt keinen Ausweg gibt, können Sie die Binärdatei / usr / bin / solc durch ein Skript wie dieses ersetzen (vergessen Sie nicht, die Originaldatei zu speichern):
#!/bin/bash # run Solidity compiler of given version, pass all parameters # you can run “SOLC_DOCKER_VERSION=0.4.20 solc --version” SOLC_DOCKER_VERSION="${SOLC_DOCKER_VERSION:-0.4.24}" docker run \ --entrypoint "" \ --tmpfs /tmp \ -v $(pwd):/project \ -v $(pwd)/node_modules:/project/node_modules \ -w /project \ ethereum/solc:$SOLC_DOCKER_VERSION \ /usr/bin/solc \ "$@"
Es lädt das Docker-Image mit der richtigen Version von solc
herunter und solc
, wechselt in das aktuelle Verzeichnis und führt /usr/bin/solc
mit den übergebenen Parametern aus. Kein sehr guter Weg, aber vielleicht passt er für einige Aufgaben zu Ihnen.
Code reduzieren
Lassen Sie uns nun die Quelle herausfinden. Theoretisch sollten Autoanalysatoren (insbesondere für die Analyse statischer Quellen) theoretisch einen Vertrag sammeln, alle Abhängigkeiten aufrufen, alles zu einem Monolithen kombinieren und analysieren. Aber wie ich bereits sagte, können Änderungen von Version zu Version schwerwiegend sein, und ich bin ständig auf die Notwendigkeit gestoßen, ein zusätzliches Verzeichnis in das Docker zu legen, es innerhalb des Pfads zu konfigurieren und all dies, damit die erforderlichen Importe korrekt abgerufen werden. Einige Analysatoren verstehen alles, der zweite ist daher keine universelle Option. Um nicht unter dem Verwerfen zusätzlicher Verzeichnisse zu leiden, ist es für Analysatoren, die eine einzelne Datei essen, bequemer, alles in einer Datei zusammenzuführen und nur zu analysieren.
Verwenden Sie dazu den normalen Trüffelabflacher .
Dies ist ein Standard-npm-Modul, es wird sehr einfach verwendet:
truffle-flattener contracts/Booking.sol > contracts/flattened.sol
: https://github.com/trailofbits/slither
Wenn Sie das Reduzieren irgendwie anpassen müssen, können Sie beispielsweise Ihr eigenes Reduzieren schreiben, bevor wir die Python-basierte Option verwendet haben: https://github.com/mixbytes/solidity-flattener
Beginnen wir mit der Analyse.
Am Beispiel desselben alten Mannes https://github.com/smartzplatform/constructor-eth-booking setzen wir die Analyse fort. Der Vertrag gibt die alte Version des Compilers "0.4.20" an, und ich habe absichtlich den alten Vertrag verwendet, um Probleme mit dem Compiler zu lösen. Die Situation wird durch die Tatsache verschlimmert, dass ein Autoanalysator, der beispielsweise Bytecode studiert, von dieser Version von solc abhängen kann, und hier können Unterschiede in den Versionen die Ergebnisse stark beeinflussen oder sogar alles kaputt machen. Selbst wenn Sie mit den neuesten Versionen alles koscher machen, können Sie dennoch auf einen Analysator stoßen, der auf die vorherige Version des Compilers abgestimmt wurde.
Tests kompilieren und ausführen
Um zu beginnen, ziehen Sie einfach das Projekt aus dem Github und versuchen Sie es zu kompilieren .:
git clone https://github.com/smartzplatform/constructor-eth-booking.git cd constructor-eth-booking npm install truffle compile
Sicher haben Sie Probleme mit der Compiler-Version. Auch Autoanalysatoren haben diese Probleme. Verwenden Sie daher alle Mittel, um den 0.4.20-Compiler abzurufen und das Projekt zu erstellen. Ich habe gerade die erforderliche Version des Compilers in truffle.js registriert und alles wurde wie oben beschrieben zusammengestellt.
Auch laufen
truffle-flattener contracts/Booking.sol > contracts/flattened.sol
Wie im Abschnitt über das Abflachen angegeben, handelt es sich um contracts/flattened.sol
Abflachen. contracts/flattened.sol
wir zur Analyse an verschiedene Analysegeräte weitergeben
Fazit zur Einleitung
Nachdem Sie nun abgeflacht haben und solc
einer beliebigen Version verwenden können, können Sie mit der Analyse beginnen. Ich werde die Probleme beim Ausführen von Trüffeln und Tests weglassen. Es gibt eine Menge Dokumentation zu diesem Thema. Verstehen Sie es selbst. Natürlich müssen die Tests erfolgreich ausgeführt werden. Um die Logik zu überprüfen, muss der Prüfer häufig seine eigenen Tests hinzufügen, potenziell undichte Stellen überprüfen, z. B. die Vertragsfunktionalität an den Grenzen von Arrays überprüfen, alle Variablen mit Tests abdecken, auch diejenigen, die ausschließlich zur Datenspeicherung bestimmt sind usw. Es gibt viele Empfehlungen, außerdem ist dies nur das Produkt, das unser Unternehmen auf den Markt bringt. Das Studium der Logik ist also eine rein menschliche Aufgabe.
Wir werden Analysatoren durchgehen, die aus unserer Sicht interessant sind, versuchen, unseren Vertrag in sie zu integrieren, und wir werden Schwachstellen künstlich einführen, um zu bewerten, wie die automatischen Analysatoren auf sie reagieren werden. Der nächste Artikel ist dem Slither-Analysator gewidmet, und im Allgemeinen sieht der Aktionsplan ungefähr wie folgt aus:
Teil 1. Einführung. Zusammenstellung, Abflachung, Versionen von Solidity (dieser Artikel)
Teil 2. Slither
Teil 3. Mithril
Teil 4. Mantikor
Teil 5. Echidna
Teil 6. Unbekanntes Werkzeug 1
Teil 7. Unbekanntes Werkzeug 2
Dieser Satz von Analysatoren wurde erhalten, weil es für den Prüfer wichtig ist, verschiedene Arten von Analysen verwenden zu können - statische und dynamische, und sie erfordern völlig unterschiedliche Ansätze. Unsere Aufgabe ist es zu lernen, wie man die grundlegenden Werkzeuge in jeder Art von Analyse verwendet und welche man wann verwendet.
Möglicherweise erscheinen im Verlauf einer detaillierten Studie neue Kandidaten zur Prüfung, oder die Reihenfolge der Artikel ändert sich. Bleiben Sie also auf dem Laufenden. Um zum nächsten Teil zu gelangen, klicken Sie hier.