Startseite
» Wissen
»
Eine Schritt-für-Schritt-Anleitung zur Prüfung eines Smart Contracts in einem Krypto-Projekt
Eine Schritt-für-Schritt-Anleitung zur Prüfung eines Smart Contracts in einem Krypto-Projekt
Ein Smart-Contract-Audit ist ein strukturierter Versuch, Schwachstellen, Missbrauchspotenzial oder unerwartete Manipulationen eines Vertrags aufzudecken. Er unterscheidet sich von der Ausführung eines Scanners, dem Ablesen eines Audit-Badges oder der Bestätigung der Quellcodeverifizierung. Nutzen Sie den unten beschriebenen Workflow, um einen bereitgestellten EVM-Contract oder eine Codebasis zu überprüfen, bevor Sie ihm größere Gelder anvertrauen.
Wichtig: Dies ist ein praktischer Bewertungsrahmen, keine Garantie für die Sicherheit eines Projekts und keine Anlageberatung. Ein Produktionsprotokoll von erheblichem Wert sollte von erfahrenen Sicherheitsexperten unabhängig geprüft werden. Die in diesem Leitfaden verwendeten Benutzeroberflächenabbildungen dienen lediglich der Veranschaulichung und sollten nicht als Beweis für ein bestimmtes Projekt oder eine bestimmte Implementierung herangezogen werden.
Audit-Checkliste auf einen Blick
Schritt
Hauptfrage
Nützliche Belege
1. Geltungsbereich
Überprüfe ich genau den Vertrag, den die Nutzer anrufen?
Adresse, Kette, Bytecode, Proxy und Implementierung
Gilt das Verhalten auch bei unerwarteten Eingaben und Sequenzen?
Unit-, Fuzz-, Invarianten- und Fork-Tests
8. Berichterstattung
Kann eine andere Person das Ergebnis reproduzieren und erneut testen?
Feststellung, Auswirkungen, Beweise, Behebung und erneuter Test des Status
Schritt 1: Bestätigen Sie den Umfang und das bereitgestellte Artefakt.
Eine Vertragsverifizierungsansicht, die die Netzwerk-, Adress-, Compilerversions- und Bytecode-Übereinstimmungsprüfungen anzeigt, die vor der Analyse protokolliert werden müssen.
Beginnen Sie mit der exakten Blockchain und Adresse. Notieren Sie die Bereitstellungsadresse, den Transaktions-Hash, die Blocknummer, die Compilerversion, die Optimierereinstellungen, die Konstruktorargumente und den Commit oder das Release, das laut Team bereitgestellt wurde. Ein Projekt kann mehrere Adressen für eine Token-, Router-, Vault-, Proxy-, Implementierungs-, Oracle- oder Testbereitstellung haben. Die Überprüfung einer falschen Adresse ist nutzlos.
Prüfen Sie, ob der verifizierte Quellcode des Explorers den bereitgestellten Bytecode reproduziert. Die Verifizierung ist nützlich, da sie die Untersuchung von Quellcode und ABI ermöglicht, stellt aber lediglich eine Identitätsprüfung dar: Sie beweist nicht die Sicherheit der Geschäftslogik. Ist der Vertrag aktualisierbar, ermitteln Sie sowohl den Proxy als auch dessen aktuelle Implementierung. Lesen Sie die Implementierungsadresse aus der Dokumentation des Proxy-Mechanismus oder den Explorer-Informationen und bestätigen Sie anschließend, dass es sich um die zu überprüfende Implementierung handelt. Der offizielle Foundry-Verifizierungsleitfaden von Etherscan dokumentiert die Verifizierung für neue und bestehende Verträge.
Definieren Sie außerdem die Abgrenzung. Berücksichtigen Sie importierte Bibliotheken, geerbte Verträge, verknüpfte Bibliotheken, bereitgestellte Hilfsverträge, Oracle-Adapter, von Benutzern erhaltene Token und privilegierte Off-Chain-Komponenten. Dokumentieren Sie, was nicht zum Geltungsbereich gehört und warum. Dadurch wird verhindert, dass eine eng gefasste Überprüfung fälschlicherweise als Überprüfung des gesamten Systems interpretiert wird.
Schritt 2: Erstellen Sie eine Einstiegspunkt- und Anlagenübersicht
Ein Funktionsinventar, das öffentliche und externe Einstiegspunkte trennt, bevor der Prüfer deren Zustandsänderungen nachverfolgt.
Listen Sie alle öffentlichen und externen Funktionen auf, einschließlich geerbter Funktionen und Fallback- oder Empfangshandlern. Geben Sie für jede Funktion an, ob sie Folgendes kann:
Landeswährung oder Token transferieren;
prägen, verbrennen, entleihen, liquidieren oder die Buchhaltung ändern;
ein Orakel, eine Gebühr, ein Limit, eine Rolle, einen Pausenstatus oder eine Implementierung ändern;
einen externen Anruf tätigen, einen Delegierungsanruf durchführen oder einen Low-Level-Anruf tätigen; oder
Daten lesen, auf die eine andere zustandsverändernde Funktion angewiesen ist.
Anschließend werden die Vermögenswerte und Vertrauensgrenzen abgebildet. Der Ablauf einer Einzahlung vom Benutzer über die Preis- und Anteilsberechnung bis hin zur Auszahlung wird verfolgt. Jede vom Aufrufer angegebene und jede aus dem Speicher geladene Adresse wird identifiziert. Es wird geprüft, welche Werte als vertrauenswürdig gelten: ein Orakel, ein Token, eine Brückennachricht, ein Verwalter, ein Rückrufempfänger oder ein Administrator. Die wichtigsten Prüfziele sind Funktionen, die benutzergesteuerte Eingaben, privilegierte Zustände, Arithmetik und einen externen Aufruf kombinieren.
Schritt 3: Sauber kompilieren und statische Analyse ausführen
Durch eine statische Codeanalyse lassen sich schnell potenzielle Probleme identifizieren, die jedoch noch anhand des tatsächlichen Codes und des Bedrohungsmodells bestätigt werden müssen.
Reproduzieren Sie den Build des Projekts mit der angegebenen Solidity-Version, den Abhängigkeitsversionen, der Optimiererkonfiguration und den Zielkettenannahmen. Behandeln Sie Compilerwarnungen als Prüfpunkte und nicht als harmlose Meldungen. Die Sicherheitsrichtlinien von Solidity empfehlen ausdrücklich, Warnungen ernst zu nehmen, Verträge verständlich zu gestalten und bekannte Compilerprobleme zu überprüfen. Konsultieren Sie die offizielle Liste bekannter Solidity-Compilerfehler, wenn die Compilerversion oder betroffene Codemuster dies erfordern.
Bei Hardhat-, Foundry- oder ähnlichen Projekten führen Sie Slither im Projektverzeichnis aus. Die offizielle Dokumentation beschreibt das Tool als statischen Analysator für Solidity und Vyper und gibt den üblichen Befehl an:
slither .
Speichern Sie die Ausgabe und priorisieren Sie jedes Ergebnis nach Auswirkung und Zuverlässigkeit. Untersuchen Sie insbesondere Ergebnisse zu willkürlichen Token-Sendungen, ungeschützten Upgrades, Reentrancy, ungeprüften Rückgabewerten, gefährlichen Delegatecalls, tx.origin, schwacher Zufälligkeit und fehlerhaften Schnittstellen. Ein Detektor kann ein falsch positives Ergebnis liefern, einen projektspezifischen wirtschaftlichen Fehler übersehen oder Code markieren, der an anderer Stelle absichtlich eingeschränkt ist. Die statische Analyse schränkt die Suche ein, ersetzt aber nicht die manuelle Überprüfung. Das Slither-Repository und die Dokumentation enthalten außerdem Ausdrucke für Einstiegspunkte, Autorisierungen, Aufrufdiagramme und Vertragszusammenfassungen, die die Überprüfung strukturieren.
Schritt 4: Externe Anrufe und Wiedereintritte manuell verfolgen
Vor einer Aktualisierung des Kontostands wird ein externer Anruf hervorgehoben. Dies verdeutlicht die Frage der Reihenfolge, die ein Prüfer bei jedem Auszahlungsvorgang untersuchen sollte.
Bei jedem externen Aufruf sollten der Zustand vor, während und nach dem Aufruf protokolliert werden. Der Aufgerufene kann ein bösartiger Smart Contract, ein Token mit Hooks, ein Callback-Empfänger oder ein anderes Protokoll sein, das eine gemeinsame Abhängigkeit ändert. Die Solidity-Dokumentation erklärt, dass eine Interaktion mit einem anderen Smart Contract die Kontrolle an diesen übertragen kann und empfiehlt das Checks-Effects-Interactions-Muster: Zuerst validieren, dann den Zustand des eigenen Smart Contracts aktualisieren und zuletzt extern interagieren.
Beschränken Sie die Suche nicht auf offensichtliche Ether-Transfers. Prüfen Sie ERC-777-Hooks, ERC-1155-Callbacks, Flash-Loan-Callbacks, beliebige Router, Oracle-Aufrufe und Aufrufe über geerbte Bibliotheken. Überprüfen Sie die funktions- und vertragsübergreifende Wiedereintrittsfähigkeit: Ein Callback kann eine andere Funktion aufrufen, die einen Zwischenzustand liest. Stellen Sie sicher, dass jeder Low-Level-Aufruf sein Erfolgsergebnis prüft und den Rückgabewert korrekt verarbeitet. Prüfen Sie, ob ein fehlgeschlagener Empfänger Auszahlungen dauerhaft blockieren oder eine Endlosschleife verursachen kann.
Dokumentieren Sie für jedes plausible Problem eine konkrete Angriffssequenz. Beispiel: Der Angreifer tätigt eine Einzahlung, initiiert eine Auszahlung, erhält eine Rückruf-Nachricht, initiiert eine zweite Auszahlung und lässt erst dann den ersten Aufruf abschließen. Falls die Sequenz aufgrund einer bestimmten Invariante oder Schutzbedingung nicht funktioniert, notieren Sie den Grund. Dadurch wird die Schlussfolgerung nachvollziehbar und nicht spekulativ.
Schritt 5: Arithmetische und betriebswirtschaftliche Invarianten testen
Eine Checkliste für Arithmetik und Geschäftslogik hebt die Grenzfälle hervor, die bei normalen Tests unter Normalbedingungen oft unberücksichtigt bleiben.
Prüfen Sie die Bedeutung jeder Einheit und Umrechnung: Wei versus Ether, Token-Dezimalzahlen, Basispunkte, Anteile versus Vermögenswerte, vorzeichenbehaftete Werte und Zeiteinheiten. Beachten Sie die Rundungsrichtung. Eine Division, die zugunsten eines Einzahlers, Kreditnehmers, Liquidators oder Gebührenempfängers gerundet wird, kann bei wiederholter Anwendung zu Wertverlusten führen. Überprüfen Sie die Multiplikation vor der Division, Mindest- und Höchstbeträge, Gebührenobergrenzen, veraltete Preise, Nullangebot, Nullsaldo sowie den ersten Einzahler bzw. letzten Abnehmer.
Solidity 0.8 und höher erkennen normalerweise arithmetische Über- und Unterläufe, aber Code innerhalb eines uncheckedBlocks kann dieses Verhalten gezielt ändern. Geprüfte Arithmetik kann außerdem dazu führen, dass ein Protokoll rückgängig gemacht oder unbrauchbar wird, wenn die Grenzwerte nicht korrekt definiert sind. Testen Sie beide möglichen Ergebnisse: Diebstahl oder fehlerhafte Abrechnung sowie einen Denial-of-Service-Angriff aufgrund eines Werts, der niemals verarbeitet werden kann.
Formulieren Sie Invarianten zunächst in einfacher Sprache, bevor Sie sie in Tests umwandeln. Beispiele hierfür sind: „Die Gesamtanzahl der Anteile entspricht den Vermögenswerten gemäß der angegebenen Rundungsregel“, „Ein Nutzer kann nicht mehr abheben, als ihm zusteht“, „Das gesamte Tokenangebot entspricht der Summe der Guthaben, sofern dieses Modell zutrifft“ und „Eine Gebühr darf ihr konfiguriertes Limit nicht überschreiten“. Vergleichen Sie die Speicherguthaben mit den tatsächlichen Tokenguthaben, da Token direkt an einen Smart Contract gesendet werden können oder sich anders verhalten als in der angenommenen ERC-20-Implementierung vorgesehen.
Schritt 6: Berechtigungen und Upgradefähigkeit prüfen
Die Überprüfung der Berechtigungen sollte jede Rolle mit ihrer Adresse, den zulässigen Aktionen, dem Übertragungsprozess und dem Upgrade-Pfad verknüpfen.
Erstellen Sie eine Berechtigungsmatrix. Identifizieren Sie für jede administrative Funktion die erforderliche Rolle, den aktuellen Inhaber, den Übertragungsmechanismus, die Verzögerung, die Multisignatur- oder Governance-Kontrolle sowie das Notfallverhalten. Achten Sie besonders auf das Erstellen, Pausieren, Ändern von Gebühren, Ändern von Oracle-Quellen, Sichern von Geldern, Aktualisieren von Code und Ändern von vertrauenswürdigen Token- oder Router-Adressen. Die Zugriffskontrolldokumentation von OpenZeppelin unterscheidet zwischen einfacher Besitzberechtigung und rollenbasierten Berechtigungen und beschreibt das Prinzip der minimalen Berechtigungen als sinnvolle Sicherheitspraxis.
Unterscheiden Sie zwischen „Der Code erlaubt dies einem Administrator“ und „Jeder beliebige Benutzer kann dies tun“. Ersteres kann ein explizites Governance- oder Verwahrungsrisiko darstellen; Letzteres eine Autorisierungsschwachstelle. Stellen Sie sicher, dass die Rollenprüfungen alle sensiblen Pfade abdecken, einschließlich interner Hilfsfunktionen, die über öffentliche Funktionen erreichbar sind. Prüfen Sie, ob ein Standardadministrator sich selbst oder anderen zusätzliche Berechtigungen erteilen kann und ob eine Eigentumsübertragung versehentlich an eine ungültige Adresse gesendet werden kann.
Überprüfen Sie bei Proxys den Initialisierer, die Implementierungsautorisierung, die Aktualisierungsverzögerung, das Speicherlayout und den Rollback- oder Notfallplan. Die Richtlinien von OpenZeppelin zu aktualisierbaren Verträgen erläutern, warum Konstruktoren den Proxy-Speicher nicht initialisieren, warum Initialisierer geschützt werden müssen, warum eine Implementierung nicht uninitialisiert bleiben sollte und warum Änderungen der Speicherreihenfolge oder -typen eine Aktualisierung beeinträchtigen können. Behandeln Sie einen Proxy-Admin-Schlüssel als Teil der Sicherheitsgrenze des Protokolls und nicht als Implementierungsdetail.
Schritt 7: Testen Sie das System mit Fuzzing, Invarianten und Forks.
Durchlaufende Kampagnen liefern nützliche Hinweise, während eine Gegenbeispiel-Spur genau zeigt, welche Sequenz untersucht werden muss.
Führen Sie Unit-Tests für das erwartete Verhalten durch und ergänzen Sie diese anschließend um Negativtests für nicht autorisierte Aufrufer, Nullwerte, Maximalwerte, abgelaufene Signaturen, veraltete Oracle-Daten, fehlgeschlagene Übertragungen und wiederholte Operationen. Testen Sie die Eingaben durch Fuzzing, anstatt nur einige wenige ausgewählte Zahlen zu prüfen. Integrieren Sie mehrere Akteure und bösartige Empfängerverträge, sofern das Design Rückrufe zulässt.
Verwenden Sie Invariantentests für Eigenschaften, die auch nach vielen zufälligen Aufrufen gültig bleiben müssen. Die Foundry-Dokumentation zu Invariantentests beschreibt zufällige Sequenzen, Fuzzing-Eingaben, Testläufe, Testtiefe, Zielverträge und Zielsender. Konfigurieren Sie die Handler so, dass die Aufrufe aussagekräftig sind. Wenn beispielsweise jede Fuzzing-Einzahlung rückgängig gemacht wird, weil der Testakteur keine Token besitzt, bedeutet ein positives Ergebnis der Invariante möglicherweise lediglich, dass sich kein nützlicher Zustand geändert hat.
Nutzen Sie nach Möglichkeit einen Fork des Zielnetzwerks, um die bereitgestellten Adressen, die aktuelle Konfiguration, das Tokenverhalten und das Proxy-Routing zu testen. Halten Sie Fork-Tests sicher und schreibgeschützt, es sei denn, Sie verwenden einen isolierten lokalen Fork. Minimieren Sie jede fehlgeschlagene Sequenz und sichern Sie das Gegenbeispiel, die Aufruferadressen, den Blockkontext, die Guthaben und die relevanten Speicherwerte. Ein erfolgreicher Test liefert Hinweise auf die getesteten Pfade, beweist aber nicht alle möglichen Pfade.
Schritt 8: Ergebnisse aufschreiben, die behoben und erneut getestet werden können
Ein hilfreicher Bericht verknüpft Schweregrad und Status mit Beweisen, einer konkreten Lösung und einer Bedingung für einen erneuten Test.
Pro Problem ist ein Datensatz zu verwenden. Ein praktischer Befund sollte Folgendes enthalten:
Titel und Ort: Vertrag, Funktion, Datei und Zeilen- oder Codeverweis.
Auswirkungen: Was kann gestohlen, eingefroren, aufgebläht, umgangen oder verfälscht werden?
Voraussetzung: die erforderlichen Berechtigungen, Guthaben, Zeitvorgaben oder Konfigurationen.
Reproduktion: eine kurze Transaktionssequenz, ein Test, eine Nachverfolgung oder ein Nachweis.
Empfehlung: eine konkrete Änderung des Codes oder der Betriebsabläufe, mit entsprechenden Kompromissen.
Status: offen, behoben, minimiert, akzeptiertes Risiko oder nicht reproduzierbar.
Wiederholungstest: der genaue Test oder die Beobachtung, die die Lösung bestätigt.
Der Schweregrad sollte die realistischen Auswirkungen und die Ausnutzbarkeit widerspiegeln, nicht wie alarmierend ein Codemuster aussieht. Erläutern Sie die zugrunde liegenden Annahmen. Ein Low-Level-Aufruf kann durch eine starke Invariante geschützt sein; eine scheinbar gewöhnliche Parameteränderung kann kritisch sein, wenn sie ein Orakel oder ein Upgrade steuert. Überprüfen Sie nach einer Korrektur die Änderungen, führen Sie den relevanten Test und die gesamte Testsuite erneut aus und prüfen Sie auf Regressionen. Wenn die bereitgestellte Adresse bereits aktualisiert oder geändert wurde, testen Sie die tatsächliche On-Chain-Implementierung und -Konfiguration erneut.
Häufige Fehler bei Audits, die es zu vermeiden gilt
„Der Quellcode ist verifiziert, also ist er sicher.“ Die Verifizierung stellt die Übereinstimmung zwischen Quellcode und Bytecode her; sie validiert nicht das Design.
„Der Scanner hat nichts gefunden, es gibt also keine Fehler.“ Tools sind am stärksten bei bekannten Mustern, während wirtschaftliche und vertragsübergreifende Fehler oft eine menschliche Analyse erfordern.
„Für das Projekt liegt ein Prüfbericht vor, die aktuelle Bereitstellung ist also abgedeckt.“ Vergleichen Sie die im Bericht enthaltenen Commits, den Umfang, die Bereitstellungsadressen, die Korrekturen und den Aktualisierungsverlauf.
„Die Fuzz-Tests waren erfolgreich, daher ist die Invariante korrekt.“ Zunächst muss bestätigt werden, dass die Invariante die beabsichtigte ökonomische Eigenschaft ausdrückt und dass die Handler sinnvolle Zustände erreichen.
„Die Administratorkontrolle ist kein Sicherheitsproblem.“ Es mag sich um eine bewusste Vertrauensannahme handeln, aber Benutzer sollten sehen können, wer Lizenzen erstellen, pausieren, Parameter ändern oder Upgrades durchführen kann.
Abschließende Selbstprüfung, bevor man dem Ergebnis vertraut.
Sie sollten diese Fragen mit Ja beantworten können:
Habe ich die genaue Kette, Adresse, den Bytecode, den Proxy, die Implementierung und die Build-Einstellungen protokolliert?
Habe ich alle Zustandsänderungspunkte und die davon betroffenen Assets erfasst?
Habe ich den Kompilationsprozess fehlerfrei abgeschlossen, alle Warnungen geprüft und die automatisierten Ergebnisse priorisiert?
Habe ich jeden externen Aufruf, Callback, Low-Level-Aufruf und Fehlerpfad nachverfolgt?
Habe ich Rundung, Grenzwerte, Nullwerte, veraltete Daten und wiederholte Aktionen getestet?
Habe ich alle privilegierten Rollen, Schlüssel, Verzögerungen, Initialisierer und Upgrade-Pfade erfasst?
Habe ich aussagekräftige Unschärfe und invariante Gegenbeispiele erhalten?
Kann ein unabhängiger Gutachter jedes Ergebnis reproduzieren und jede Korrektur überprüfen?
Lautet eine der Antworten „Nein“, kennzeichnen Sie das Audit als unvollständig und geben Sie die fehlenden Nachweise an. Eine transparente Einschränkung ist aussagekräftiger als eine vage Schlussfolgerung wie „sicher“. Die Sicherheit von Smart Contracts ist ein fortlaufender Prozess: Jedes Upgrade, jede Änderung von Abhängigkeiten, jede neue Integration und jede Änderung von Berechtigungen kann einen neuen Prüfpunkt schaffen.