Liste häufiger Schwachstellen
Schwachstellen in Smart Contracts können nach ihrer Bereitstellung katastrophale und oft irreversible Folgen haben. Die Ausnutzung dieser Schwachstellen stellt eine große Herausforderung für die Blockchain-Sicherheit dar, führt zum Diebstahl von Vermögenswerten im Wert von Milliarden von Dollar und untergräbt das Vertrauen der Nutzer. Das Erreichen einer robusten Smart-Contract-Sicherheit ist daher von größter Bedeutung. Es erfordert mehr als nur die Bereitstellung von Code auf der Blockchain. Es erfordert strenge Praktiken für sicheres Programmieren, gründliche Tests und oft auch unabhängige Smart-Contract-Audits, um Probleme zu identifizieren, bevor sie ausgenutzt werden können. Das Verständnis häufiger Fallstricke – von Reentrancy-Angriffen wie dem berüchtigten DAO-Hack bis hin zu subtilen Logikfehlern und Ethereum-Schwachstellen wie Ganzzahlüberläufen – ist der erste entscheidende Schritt zur Risikominderung. Diese Seite bietet einen wesentlichen Überblick über häufige Schwachstellen von Smart Contracts, um das Bewusstsein zu schärfen und eine sicherere Entwicklung im dezentralen Ökosystem zu fördern.
- Reentrancy
- Unexpected Ether
- DoS
- Overflow
- tx.origin Authentication
- Access Control
- Weak Randomness
- Hash Collision
- Precision Loss
- msg.value in loop
- ecrecover
- Replay
- Inheritance Order
- MEV
Reentrancy ist eine der frühesten und verheerendsten Schwachstellen von Smart Contracts. Sie war verantwortlich für bedeutende historische Exploits wie den Hack von The DAO, der zu einem Hard Fork von Ethereum führte. Sie bleibt eine kritische Bedrohung mit potenziell entsetzlichen Folgen, die Verträge um alle Gelder leeren kann.
Die Schwachstelle ergibt sich aus einem verbreiteten Programmier-Antipattern, das oft aus Gewohnheiten des Web2 übernommen wird: externe Interaktionen (wie die Übertragung von Ether oder der Aufruf eines anderen Vertrags) durchzuführen, bevor der interne Zustand des Vertrags (z. B. Benutzersalden) aktualisiert wird. Wenn ein Vertrag Ether sendet oder einen externen Vertrag aufruft, überträgt er vorübergehend die Ausführungskontrolle. Ein Angreifer, der den empfangenden Vertrag kontrolliert, kann dies ausnutzen, indem er sofort einen Rückruf in den ursprünglichen Vertrag tätigt, oft über die receive- oder fallback-Funktion, die durch die Ether-Übertragung ausgelöst wird, oder über Hooks von Token-Standards.
Da der Zustand des ursprünglichen Vertrags (z. B. das Guthaben des Angreifers) noch nicht aktualisiert wurde, besteht der wiedereintretende Aufruf die anfänglichen Prüfungen. Dies ermöglicht es dem Angreifer, die Aktion (wie das Abheben von Geldern) mehrmals innerhalb derselben Transaktion zu wiederholen. Diese rekursive Schleife setzt sich fort, bis die Gelder abgezogen sind oder die Gaslimits erreicht werden.
Während der klassische Angriff eine einzige Funktion betrifft, existieren komplexere Varianten, wie die funktionsübergreifende Reentrancy (Ausnutzung gemeinsamer Zustände zwischen Funktionen) und die Read-Only-Reentrancy (Manipulation von Zustandslesevorgängen über View-Funktionen).
Die primäre Verteidigung ist das Checks-Effects-Interactions (CEI) Muster: Führen Sie alle notwendigen Prüfungen durch, aktualisieren Sie dann die Zustandsvariablen (Effekte) und interagieren Sie erst nach diesen Aktualisierungen mit externen Verträgen.
Die Schwachstelle "Kontostand-Manipulation durch unerwarteten Ether-Empfang" entsteht, weil Solidity Smart Contracts Ether durch Mechanismen empfangen können, die ihre definierten `receive`- und `fallback`-Funktionen umgehen. Die Hauptmethoden für diese "unerwartete" Ether-Zuführung sind der `selfdestruct`-Opcode, der den Kontostand eines sich selbst zerstörenden Vertrags zwangsweise an eine bestimmte Adresse überweist, und Coinbase-Transaktionen (Blockbelohnungen).
Das Kernproblem besteht darin, dass diese erzwungenen Überweisungen den Ether-Kontostand des Vertrags (`address(this).balance`) direkt erhöhen, ohne die programmierte Logik des Vertrags zur Verarbeitung eingehender Gelder auszulösen. Dies bricht eine häufige, oft implizite Annahme oder Invariante: dass der tatsächliche Kontostand des Vertrags genau die Summe der Gelder widerspiegelt, die über seine vorgesehenen `payable`-Funktionen verarbeitet oder intern verbucht wurden.
Verträge, die `address(this).balance` fälschlicherweise für kritische Logikprüfungen verwenden, werden anfällig. Zum Beispiel könnten Verträge prüfen, ob `address(this).balance == erwarteterBetrag`. Ein Angreifer kann dies ausnutzen, indem er `selfdestruct` verwendet, um eine kleine Menge Ether zu senden und so den Kontostand zu manipulieren. Dies kann führen zu:
Denial of Service (DoS): Strikte Gleichheitsprüfungen können dauerhaft verletzt werden, wodurch Funktionen unbrauchbar werden.
Logikmanipulation: Schwellenwerte können vorzeitig erreicht werden, was unbeabsichtigte Zustandsänderungen oder Auszahlungen auslöst.
`assert`-Verletzungen: In seltenen Fällen kann der unerwartete Kontostand zu internen Zustandsinkonsistenzen führen, die einen `assert()`-Fehlschlag verursachen, wodurch das gesamte Transaktionsgas verbraucht wird.
Die grundlegende Abhilfe besteht darin, sich in der Vertragslogik niemals auf `address(this).balance` zu verlassen. Stattdessen müssen Verträge eine genaue interne Buchführung mithilfe dedizierter Zustandsvariablen führen und diese nur innerhalb legitimer Funktionen zur Geldverarbeitung aktualisieren. Alle kritischen Prüfungen und Zustandsübergänge müssen auf diesen zuverlässigen internen Variablen basieren.
Denial-of-Service (DoS)-Schwachstellen in Solidity Smart Contracts zielen darauf ab, die beabsichtigte Funktionalität des Vertrags zu stören oder vollständig zu unterbinden, wodurch legitime Benutzer am Zugriff auf seine Dienste gehindert werden. Der „Dienst“, der verweigert wird, ist die Fähigkeit des Vertrags, seine Funktionen wie programmiert und von seinen Benutzern erwartet auszuführen.
Angreifer erreichen dies, indem sie Fehler in der Code-Logik des Vertrags ausnutzen, Ressourcenbeschränkungen (hauptsächlich Gas) manipulieren oder externe Abhängigkeiten ausnutzen.
Häufige Muster sind:
Gaserschöpfung (Gas Exhaustion): Erstellen von Transaktionen oder Manipulieren des Zustands, um die Ausführungskosten einer Funktion das Block-Gaslimit oder das Transaktions-Gaslimit übersteigen zu lassen. Dies beinhaltet oft das Auslösen rechenintensiver Operationen oder, sehr häufig, die Iteration über unbegrenzte Arrays, deren Größe unbegrenzt wachsen kann. Wenn die Datenmenge wächst, übersteigen die Gaskosten der Schleife die Limits, wodurch die Funktion unbrauchbar wird.
Unerwartete Reverts: Verursachen, dass kritische Funktionen unerwartet zurückgesetzt werden (revert). Dies kann durch das Erzwingen fehlgeschlagener externer Aufrufe (z. B. Senden von Geldern an einen Vertrag, der sie ablehnt), das Manipulieren des Zustands, um `require`-Bedingungen fehlschlagen zu lassen, oder andere unbehandelte Ausnahmen ausgelöst werden. Wenn ein Vertrag von einem externen Aufruf abhängt, der fehlschlägt (möglicherweise böswillig herbeigeführt), kann der gesamte Vorgang blockiert werden.
Die Unveränderlichkeit von Smart Contracts macht DoS besonders schwerwiegend. Ein erfolgreicher Angriff kann zu dauerhaft gesperrten Geldern oder nicht wiederherstellbarer Funktionalität führen, da der Vertragscode nach der Bereitstellung nicht einfach korrigiert werden kann.
Die Minderung basiert auf sicheren Programmierpraktiken: Verwendung begrenzter Operationen anstelle von unbegrenzten Schleifen, Bevorzugung von Pull-Payment-Mustern gegenüber dem Senden von Geldern an Benutzer (Push-Payments), was Übertragungsfehler isoliert, robuste Handhabung von Fehlern bei externen Aufrufen (z. B. Verwendung von `try/catch`, wo angebracht) und sorgfältiges Gas-Management.
Schwachstellen durch Integer-Überläufe (Integer Overflow) und -Unterläufe (Integer Underflow) stellen eine ständige Bedrohung im Bereich der Sicherheit von Smart Contracts dar. Sie resultieren aus den fundamentalen Beschränkungen der Arithmetik mit Ganzzahlen fester Größe. Ein Überlauf tritt auf, wenn das Ergebnis einer arithmetischen Operation den Maximalwert für ihren Typ überschreitet, und ein Unterlauf, wenn das Ergebnis unter den Minimalwert fällt.
Historisch gesehen führten diese arithmetischen Fehler in Solidity-Versionen vor 0.8.0 dazu, dass der Wert stillschweigend "herumwickelte" (wrap around). Zum Beispiel führte das Addieren von 1 zum maximalen uint8-Wert (255) zu 0, und das Subtrahieren von 1 von 0 führte zu 255. Dieses vorhersagbare, aber ungeprüfte Verhalten war eine Hauptquelle für Exploits und ermöglichte es Angreifern, Token-Guthaben zu manipulieren (z. B. riesige Mengen zu prägen oder Gelder durch das "Herumwickeln" von Guthaben abzuziehen), kritische Sicherheitsprüfungen zu umgehen, den Vertragszustand zu beschädigen oder einen Denial-of-Service (Dienstverweigerung) zu verursachen.
Solidity Version 0.8.0 führte eine entscheidende Sicherheitsverbesserung ein: Standardmäßige arithmetische Operationen (+, -, *, /) führen nun standardmäßig Überlauf- und Unterlaufprüfungen durch. Wenn eine Operation zu einem "Wrap Around" führen würde, wird die Transaktion stattdessen zurückgesetzt (revert), wodurch eine stille Zustandsbeschädigung verhindert wird.
Das Risiko ist jedoch nicht vollständig eliminiert. Entwickler können diese Standardprüfungen explizit mithilfe von `unchecked`-Blöcken umgehen, oft zur Gas-Optimierung. Code innerhalb eines `unchecked`-Blocks kehrt zum gefährlichen, stillschweigenden Wrap-Around-Verhalten der Versionen vor 0.8.0 zurück und erfordert eine äußerst sorgfältige Validierung. Ebenso profitiert Low-Level-EVM-Assemblycode nicht von den Prüfungen von Solidity und erfordert ein manuelles Sicherheitsmanagement für arithmetische Operationen. Zusätzlich bleiben Schiebeoperationen (<<, >>) auch im Standardmodus >=0.8.0 ungeprüft und schneiden Ergebnisse ab. Unsachgemäße Typumwandlung (Type Casting) (z. B. die Konvertierung eines großen uint256 in einen kleineren Typ wie uint8) kann ebenfalls zur Wertabschneidung führen und potenziell unerwartetes Verhalten bei nachfolgenden Berechnungen verursachen.
Obwohl Integer-Überläufe/-Unterläufe im modernen Solidity standardmäßig erheblich abgeschwächt sind, bleiben sie daher eine relevante Schwachstelle, insbesondere in Legacy-Verträgen (Altverträgen), Code, der `unchecked`-Blöcke oder Assembly verwendet, sowie durch spezifische ungeprüfte Operationen oder unachtsame Typumwandlungen.
Die Verwendung von `tx.origin` für Autorisierungslogik in Solidity Smart Contracts stellt eine kritische Sicherheitslücke dar. Sie resultiert aus einem grundlegenden Missverständnis seines Verhaltens im Vergleich zu `msg.sender`. Während `tx.origin` konsistent das EOA (externally owned account / extern verwaltetes Konto) identifiziert, das die Transaktion initiiert hat, identifiziert `msg.sender` den unmittelbaren Aufrufer. Die Verwendung von `tx.origin` für Berechtigungsprüfungen versäumt es, die Entität zu validieren, die direkt mit dem Vertrag interagiert.
Diese Schwachstelle öffnet die Tür für Angriffe im Phishing-Stil, bei denen ein bösartiger zwischengeschalteter Vertrag (intermediary contract), der von einem privilegierten Benutzer aufgerufen wird, geschützte Funktionen auf dem Zielvertrag erfolgreich aufrufen kann. Die `tx.origin`-Prüfung validiert fälschlicherweise den ursprünglichen Benutzer, wodurch der bösartige Vertrag Aktionen mit der Berechtigung des Benutzers ausführen kann. Die Konsequenzen reichen vom direkten Diebstahl von Ether und Token bis hin zur unbefugten Manipulation des Vertragszustands, was potenziell zu erheblichen finanziellen Verlusten und irreparablen Schäden an der Integrität und Reputation des Protokolls führen kann.
Unzureichende Zugriffskontrolle ist eine kritische Schwachstelle in Solidity Smart Contracts, bei der Einschränkungen, wer welche Funktionen ausführen darf, fehlen oder unsachgemäß implementiert sind. Da Solidity keine eingebauten Berechtigungsmodelle besitzt, müssen Entwickler manuell Prüfungen hinzufügen, oft unter Verwendung von Modifikatoren wie onlyOwner oder durch Implementierung einer rollenbasierten Zugriffskontrolle (RBAC). Wenn dies nicht korrekt geschieht, entstehen Sicherheitslücken.
Diese Schwachstelle äußert sich typischerweise in Form von ungeschützten Funktionen. Funktionen, die für administrative oder sensible Operationen vorgesehen sind – wie die Übertragung des Vertragsbesitzes (changeOwner), das Abheben von Geldern (withdraw), das Pausieren des Vertrags, das Prägen von Tokens oder sogar die Zerstörung des Vertrags (selfdestruct) – könnten für jeden externen Account aufrufbar bleiben. Dies geschieht oft aufgrund fehlender Zugriffskontroll-Modifikatoren oder falscher Funktionssichtbarkeit (z.B. sind Funktionen standardmäßig public, wenn nichts anderes angegeben ist). Offengelegte Initialisierungsfunktionen, die nur einmal ausgeführt werden sollten, können ebenfalls ein Angriffsvektor sein, wenn sie nach der Bereitstellung aufrufbar bleiben und Angreifern potenziell ermöglichen, den Besitz oder kritische Parameter zurückzusetzen.
Die Folgen sind schwerwiegend und reichen von unbefugten Benutzern, die administrative Privilegien erlangen, bis hin zum vollständigen Diebstahl der vom Vertrag verwalteten Gelder oder zur irreversiblen Zerstörung des Vertrags selbst. Reale Exploits wie die Parity Wallet-Vorfälle und der LAND Token Hack zeigen das verheerende Potenzial unzureichender Zugriffskontrolle.
Zur Risikominderung gehört die rigorose Anwendung von Zugriffskontrollprüfungen auf alle sensiblen Funktionen, die Einhaltung des Prinzips der geringsten Rechte (Principle of Least Privilege), die Verwendung etablierter Muster wie Ownable oder RBAC (oft über Bibliotheken wie die von OpenZeppelin) sowie die Durchführung gründlicher Tests und Audits.
Die Generierung sicherer Zufälligkeit auf der Ethereum Virtual Machine ist aufgrund ihrer deterministischen Natur, die für den Netzwerkkonsens unerlässlich ist, eine Herausforderung. Dies erzeugt eine „Entropie-Illusion“, bei der scheinbar zufällige Werte, die rein aus On-Chain-Daten abgeleitet werden, tatsächlich vorhersagbar sind.
Entwickler missbrauchen oft leicht verfügbare Blockvariablen wie block.timestamp, blockhash und den nach dem Merge eingeführten prevrandao (zugänglich über block.difficulty) als Quellen für Pseudozufälligkeit. Diese Variablen sind unsicher, da sie vorhergesagt oder beeinflusst werden können.
Miner (in Proof-of-Work) oder Validatoren (in Proof-of-Stake) können diese Werte bis zu einem gewissen Grad manipulieren, um einen unfairen Vorteil zu erlangen, oft als Teil von MEV-Strategien (Maximal Extractable Value). Entscheidend ist, dass selbst normale Benutzer oder Angreifer-Verträge oft Ergebnisse vorhersagen können, wenn die Zufallslogik nur auf Eingaben basiert, die vor oder während der Transaktionsausführung bekannt sind, was Exploits wie Front-Running ermöglicht. Diese Vorhersagbarkeit untergräbt die Fairness von Anwendungen wie Lotterien, Spielen und NFT-Mints.
Eine Hash-Kollision durch `abi.encodePacked` mit mehreren dynamischen Typen entsteht nicht aufgrund von Schwächen der zugrundeliegenden Keccak-256-Hashfunktion, sondern durch die Art und Weise, wie Daten vor dem Hashing kodiert werden – speziell bei der Verwendung der Solidity-Funktion `abi.encodePacked`. Im Gegensatz zum Standard `abi.encode`, das Argumente auf 32 Bytes auffüllt (Padding) und Längenpräfixe für dynamische Typen enthält, erzeugt `abi.encodePacked` eine kompakte, nicht standardisierte Kodierung durch die Verkettung von Argumenten mit der minimal benötigten Anzahl an Bytes. Dabei wird das Padding für kleine statische Typen weggelassen und – was entscheidend ist – die Längeninformation für dynamische Typen wie `string`, `bytes` oder dynamische Arrays fehlt.
Das Kernproblem tritt auf, wenn `abi.encodePacked` mit zwei oder mehr aufeinanderfolgenden Argumenten dynamischen Typs verwendet wird. Da die Länge jedes dynamischen Arguments nicht mitkodiert wird, wird die Grenze zwischen ihnen in der resultierenden Byte-Sequenz mehrdeutig. Diese Mehrdeutigkeit ermöglicht es, oft auf triviale Weise, unterschiedliche Sätze logischer Eingaben zu konstruieren, die exakt dieselbe gepackte Byte-Sequenz erzeugen. Beispielsweise liefert `abi.encodePacked("a", "bc")` dieselbe Byte-Ausgabe wie `abi.encodePacked("ab", "c")`.
Wenn diese identische Byte-Ausgabe anschließend gehasht wird (z. B. `keccak256(abi.encodePacked(...))`), führt dies zum selben Hash-Wert – einer durch die Kodierung verursachten Hash-Kollision.
Diese Schwachstelle der Kodierungskollision kann auf verschiedene Arten ausgenutzt werden, wenn der resultierende Hash in einem sicherheitsrelevanten Kontext verwendet wird:
Umgehung der Signaturverifizierung: Ein Angreifer kann eine gültige Signatur, die für einen bestimmten Parametersatz erstellt wurde, entwenden und sie mit einem anderen, bösartigen Parametersatz wiederverwenden, der einen kollidierenden Hash erzeugt. Die Signaturverifizierung des Vertrags (mittels `ecrecover`) ist dann erfolgreich und ermöglicht eine unautorisierte Ausführung.
Zustandsbeschädigung durch Kollisionen von Mapping-Schlüsseln: Wird der kollisionsanfällige Hash als Schlüssel in einem Mapping (`mapping(bytes32 => ...)`) verwendet, kann ein Angreifer Eingaben so konstruieren, dass ein Schlüssel generiert wird, der mit dem Schlüssel eines legitimen Nutzers kollidiert. Dies kann potenziell dessen Daten überschreiben, Zugriffskontrollen umgehen oder einen Denial of Service verursachen.
Probleme bei der Nachrichtenauthentifizierung: Die Schwachstelle untergräbt Prüfungen, die auf den Hash zur Sicherstellung der Datenintegrität angewiesen sind, da unterschiedliche logische Nachrichten nach dem Hashing identisch erscheinen können.
Die Folgen einer erfolgreichen Ausnutzung können schwerwiegend sein und umfassen unter anderem: Unautorisierten Zugriff auf Funktionen oder Daten, direkten Diebstahl von Geldern (Fund Theft), kritische Zustandsbeschädigung und Denial of Service (DoS).
Schwachstellen durch Präzisionsverlust entstehen durch die Abhängigkeit von Ganzzahlarithmetik und die fehlende native Unterstützung für Fließkommazahlen. Dieses Design priorisiert die deterministische Ausführung, erfordert jedoch von Entwicklern die manuelle Verwaltung von Bruchwerten, was Fehlermöglichkeiten schafft.
Das Kernproblem ist die Kürzung bei der Ganzzahldivision: Solidity verwirft Reste und rundet Divisionsergebnisse in Richtung Null. Dieses vorhersagbare Verhalten kann ausgenutzt werden, oft durch Muster wie:
Division vor Multiplikation: Die Berechnung von (a / b) * c anstelle von (a * c) / b kürzt das Zwischenergebnis a / b und verstärkt den Präzisionsverlust.
Abrunden auf Null: Wenn der Zähler A kleiner als der Nenner B ist (und beide positiv sind), ergibt A / B immer 0. Dies ist riskant bei Berechnungen mit kleinen Gebühren, Belohnungen oder Token-Umwandlungen.
Angreifer nutzen diese mathematischen Eigenschaften aus, um die Vertragslogik zu manipulieren und finanziellen Gewinn zu erzielen. Gängige Strategien sind unter anderem:
Zustands-/Preismanipulation: Auslösen von Rundungsfehlern, um kritische Protokollwerte wie Wechselkurse, Pool-Reserven, Tresoranteilspreise oder Sicherheitenquoten zu verzerren, die dann in nachfolgenden Transaktionen ausgenutzt werden können.
Angriff auf Randfälle: Verwendung von Transaktionen mit sehr kleinen Eingaben oder Eingaben, die darauf ausgelegt sind, mit großen internen Werten zu interagieren, um die Auswirkungen der Kürzung zu maximieren, was oft dazu führt, dass Berechnungen Null ergeben.
Erfolgreiche Angriffe durch Präzisionsverlust können zu erheblichen negativen Konsequenzen führen:
Reduzierte Kosten: Angreifer zahlen geringere oder gar keine Gebühren/Kosten.
Überhöhte Gewinne: Angreifer erhalten unrechtmäßig mehr Token, Anteile oder Belohnungen.
Arbitragemöglichkeiten: Schaffung künstlicher Preisunterschiede innerhalb des Protokolls, die Angreifer ausnutzen können.
Umgehung von Risikomechanismen: Umgehung von Liquidationen oder anderen Sicherheitsprüfungen aufgrund ungenauer Berechnungen.
Allmähliche Erschöpfung von Geldern: Abschöpfen von Werten durch wiederholte Transaktionen, die winzige Rundungsfehler ausnutzen („1-Wei-Angriffe“).
Die Minderung erfordert eine sorgfältige Handhabung der Arithmetik, wie z. B. die Durchführung von Multiplikationen vor Divisionen, die Verwendung numerischer Skalierung (Simulation von Festkommaarithmetik), den Einsatz spezialisierter mathematischer Bibliotheken und die Implementierung geeigneter Rundungslogik. Standardmäßige Überlaufprüfungen (wie SafeMath oder Solidity >=0.8) verhindern keinen Präzisionsverlust durch Division.
Diese Schwachstelle tritt auf, wenn ein Smart Contract msg.value innerhalb einer Schleife unsachgemäß verwendet. Das Kernproblem ergibt sich aus der Tatsache, dass msg.value während des gesamten Ausführungskontexts einer Transaktion konstant bleibt. Wenn eine Schleife mehrmals durchläuft und in jeder Iteration Prüfungen oder Aktionen auf Basis dieses anfänglichen msg.value durchführt, ohne den kumulativen Wert, der über diese Iterationen hinweg verarbeitet oder ausgegeben wurde, korrekt zu verfolgen, entsteht eine Ausnutzungsmöglichkeit.
Ein Angreifer kann dies ausnutzen, indem er einen bestimmten Betrag an Ether sendet, um die anfällige Funktion auszulösen. Innerhalb der Schleife können Prüfungen wie require(msg.value >= amount_per_item) wiederholt erfolgreich sein, oder Zustandsaktualisierungen verwenden möglicherweise fälschlicherweise mehrmals den vollen anfänglichen msg.value. Dies geschieht, weil die Vertragslogik den Wert, der in früheren Iterationen derselben Schleife effektiv 'ausgegeben' oder zugewiesen wurde, nicht berücksichtigt.
Dieser Fehler ermöglicht es einem Angreifer, Aktionen auszulösen (wie Ether-Überweisungen oder interne Guthabenbuchungen), deren Gesamtwert den Ether-Betrag, den er tatsächlich mit der Transaktion gesendet hat, erheblich übersteigt.
ecrecover ist ein wesentlicher EVM-Precompile, der es Smart Contracts ermöglicht, die Adresse des Unterzeichners aus einem Nachrichten-Hash und einer ECDSA-Signatur (v, r, s) wiederherzustellen. Dies ermöglicht entscheidende Funktionalitäten wie die Überprüfung von Off-Chain signierten Nachrichten für Meta-Transaktionen oder Permit-Funktionen. Seine direkte Verwendung birgt jedoch erhebliche, oft unterschätzte Sicherheitsrisiken, wenn sie nicht sorgfältig gehandhabt wird.
Anfälligkeit durch Rückgabe der Nulladresse: Ein kritisches Problem ergibt sich aus der besonderen Fehlerbehandlung von `ecrecover`. Bei Vorlage einer ungültigen oder mathematisch unmöglichen Signatur macht es die Transaktion nicht rückgängig. Stattdessen schlägt es stillschweigend fehl und gibt die Nulladresse (`address(0)`) zurück. Verträge, die `ecrecover` aufrufen, aber keine Überprüfung auf die Nulladresse durchführen, sind hochgradig anfällig. Ein Angreifer kann absichtlich ungültige Signaturdaten übermitteln, wodurch `ecrecover` `address(0)` zurückgibt. Wenn diese Ausgabe nicht explizit überprüft und zurückgewiesen wird, könnte der Vertrag fälschlicherweise fortfahren und `address(0)` als legitimen Unterzeichner behandeln. Dies kann schwerwiegende Folgen haben, wie unbefugte Zustandsänderungen, falsche Ereignis-Emissionen oder die Gewährung von Berechtigungen, insbesondere wenn die Nulladresse besondere Privilegien oder einen bedeutsamen Zustand innerhalb der spezifischen Logik des Vertrags hat. Robuster Code muss immer `recoveredAddress != address(0)` unmittelbar nach dem `ecrecover`-Aufruf validieren.
Anfälligkeit durch Signaturformbarkeit (Malleability): Das zweite große Risiko ergibt sich aus einer inhärenten Eigenschaft des ECDSA-Algorithmus selbst: der Signaturformbarkeit. Für jede gegebene Nachricht und jeden privaten Schlüssel können mehrere unterschiedliche, aber kryptografisch gültige Signaturdarstellungen existieren (insbesondere kann eine Signatur, die die Komponente `s` verwendet, oft in eine gültige Signatur unter Verwendung von `n-s` umgewandelt werden, wobei `n` die Ordnung der Kurve ist). Dies wird zu einer Schwachstelle, wenn Verträge fälschlicherweise davon ausgehen, dass eine Signatur für eine Nachricht eindeutig ist. Angreifer können dies ausnutzen, indem sie Eindeutigkeitsprüfungen umgehen. Wenn ein Vertrag beispielsweise den Hash der Signatur selbst als Nonce verwendet, um Replay-Angriffe zu verhindern (ein fehlerhaftes Muster), kann ein Angreifer eine gültige Signatur nehmen, ihr formbares Gegenstück berechnen und es erneut einreichen, um die Aktion auszuführen, da sich die Signatur-Hashes unterscheiden werden. Es kann auch zu unerwartetem Verhalten oder Replays in Systemen führen, die eine bestimmte Signaturform erwarten, wenn externe Systeme oder Teile der Vertragslogik nicht darauf ausgelegt wurden, beide gültigen Signaturformen zu verarbeiten. Eine wirksame Abhilfe besteht darin, die Signatur-Kanonisierung zu erzwingen – eine Prüfung, die in Standardbibliotheken wie der ECDSA von OpenZeppelin robust implementiert ist und die der direkten Verwendung von `ecrecover` vorzuziehen ist.
Cross-Chain-Replay-Angriff (CCRA): Eine Transaktion, die gültig auf einer EVM-Chain ausgeführt wurde, wird abgefangen und erfolgreich auf einer anderen EVM-Chain erneut eingereicht. Dies nutzt Ähnlichkeiten in Transaktionsformaten und Signaturen über verschiedene Chains hinweg aus, insbesondere wenn Transaktionen keine eindeutigen Chain-Identifikatoren (Chain-IDs) enthalten. Der Hard Fork von Ethereum/Ethereum Classic ist ein klassisches Beispiel, bei dem dieses Risiko eintrat. EIP-155 wurde eingeführt, um dies abzuschwächen, indem die Chain-ID in Standard-Transaktionssignaturen eingebettet wird, wodurch diese kettenspezifisch werden. Smart Contracts, die benutzerdefinierte Signaturprüfungen verwenden, müssen jedoch ebenfalls explizit die Chain-ID überprüfen. Der 20-Millionen-Dollar-Exploit bei Optimism gegen Wintermute resultierte aus einer solchen fehlenden Chain-ID-Überprüfung in einem kettenübergreifend eingesetzten Vertrag.
Replay auf Smart-Contract-Ebene (innerhalb derselben Chain): Eine signierte Nachricht oder Transaktion wird erneut gegen denselben Smart Contract oder möglicherweise einen anderen Vertrag auf derselben Chain ausgeführt (replayed). Dies nutzt typischerweise Schwachstellen innerhalb der eigenen Logik des Vertrags aus, insbesondere in benutzerdefinierten Signaturprüfungsschemata, die für Funktionen wie Meta-Transaktionen oder ERC-20-Permit-Funktionen verwendet werden. Der häufigste Fehler ist das Fehlen oder die unsachgemäße Implementierung einer Nonce auf Anwendungsebene. Eine Nonce („number used once“ - einmalig verwendete Nummer) ist ein eindeutiger Zähler, der dem Unterzeichner zugeordnet ist. Sie muss im Hash der signierten Nachricht enthalten sein und vom Vertrag on-chain verfolgt werden, um sicherzustellen, dass jede spezifische Signatur nur eine Aktion autorisiert.
Die Unterstützung von Mehrfachvererbung in Solidity ermöglicht es einem Vertrag, gleichzeitig Merkmale von mehreren übergeordneten Verträgen zu erben. Obwohl dies für die Wiederverwendung von Code leistungsfähig ist, führt es zu potenzieller Mehrdeutigkeit, bekannt als das „Diamantproblem“: wenn zwei oder mehr Basisverträge eine Funktion mit demselben Namen und denselben Parametern definieren.
Um dies zu lösen, verwendet Solidity den C3-Linearisierungsalgorithmus, um eine einzige, deterministische Methodenauflösungsreihenfolge (Method Resolution Order, MRO) für jeden Vertrag festzulegen. Diese MRO schreibt die genaue Reihenfolge vor, in der Basisverträge bei der Auflösung von Funktionsaufrufen überprüft werden.
Die Schwachstelle ergibt sich direkt daraus, wie diese MRO bestimmt wird. Der entscheidende Faktor ist die Reihenfolge, in der der Entwickler die Basisverträge in der `is`-Anweisung auflistet. Solidity erfordert die Auflistung der Verträge von „am meisten basisähnlich“ bis „am meisten abgeleitet“. Diese festgelegte Reihenfolge beeinflusst direkt die endgültige MRO, die vom C3-Algorithmus generiert wird.
Die Schwachstelle tritt auf, wenn der Entwickler eine Vererbungsreihenfolge angibt, die nicht der beabsichtigten logischen Hierarchie oder Priorität entspricht. Wenn ein allgemeinerer Vertrag nach einem spezifischeren aufgeführt wird oder die Reihenfolge anderweitig dazu führt, dass die MRO eine unbeabsichtigte Funktionsimplementierung priorisiert, kann sich der Vertrag unerwartet verhalten. Beispielsweise könnte ein Aufruf zu einer Basisfunktion aufgelöst werden, der kritische Sicherheitsprüfungen oder aktualisierte Logik fehlen, die in einer beabsichtigten, aber falsch geordneten Überschreibung eines abgeleiteten Vertrags implementiert wurden.
Zu den Folgen der Ausführung der falschen Funktion aufgrund einer falschen Vererbungsreihenfolge gehören die Umgehung von Zugriffskontrollen, die Ausführung veralteter oder falscher Geschäftslogik, Zustandsbeschädigung und potenzielle finanzielle Verluste. Im Wesentlichen weicht der tatsächliche Ausführungsfluss des Vertrags vom Design des Entwicklers ab, was Sicherheit und Funktionalität untergräbt.
Maximal Extrahierbarer Wert (Maximal Extractable Value - MEV) bezeichnet den Gewinn, den Block-Produzenten und spezialisierte Sucher (Searcher) durch die Manipulation der Einbeziehung und Reihenfolge von Transaktionen innerhalb eines Blocks erzielen können, der über die Standard-Blockbelohnungen und Gasgebühren hinausgeht. Ursprünglich als "Miner Extrahierbarer Wert" bezeichnet, entwickelte sich der Name zu "Maximal", um die Gier nach Belohnung widerzuspiegeln.
MEV entsteht, weil ausstehende Transaktionen oft in einem öffentlichen Wartebereich namens Mempool liegen, der für jeden einsehbar ist. Block-Produzenten haben die Befugnis, über die endgültige Reihenfolge der Transaktionen in einem Block zu entscheiden. Automatisierte Bots, die von "Suchern" (Searchers) betrieben werden, überwachen ständig den Mempool, simulieren potenzielle Ergebnisse und nutzen profitable Gelegenheiten aus, indem sie Transaktionen strategisch anordnen, oft unter Verwendung von Gasgebotsverfahren (Priority Gas Auctions - PGAs), um eine bevorzugte Platzierung sicherzustellen.
Gängige MEV-Strategien, die oft als Angriffe betrachtet werden, umfassen:
Front-Running: Platzieren der Transaktion eines Angreifers vor der Transaktion eines Opfers (z. B. ein großer DEX-Handel), um vom erwarteten Preiseffekt zu profitieren.
Sandwich-Attacken (Sandwich Attacks): Eine Kombination aus Front-Running und Back-Running (dem Folgen) einer DEX-Transaktion eines Opfers, um die durch die Manipulation verursachte Preisdifferenz (Slippage) zu erfassen.
JIT-Liquidität (JIT Liquidity): Vorübergehendes Hinzufügen und Entfernen von Liquidität rund um einen großen Swap auf DEXs mit konzentrierter Liquidität, um Gebühren zu erfassen.
Orakel-Manipulation (Oracle Manipulation): Ausnutzung von Preisorakel-Updates oder Ungenauigkeiten zum eigenen Vorteil, was oft Kreditprotokolle betrifft.
Weitere Arten umfassen NFT-Sniping und Dust-Attacken (Staub-Angriffe).
Die Folgen für Benutzer umfassen erhöhte Transaktionskosten aufgrund von Gaskriegen (Gas Wars), schlechtere Ausführungspreise (Slippage) bei Trades, verschärften impermanenten Verlust (Impermanent Loss) für Liquiditätsanbieter und unfair ausgelöste Liquidationen.
Minderungsstrategien zielen darauf ab, die negativen Auswirkungen von MEV zu reduzieren. Das Senden von Transaktionen über private Mempools oder Relais (wie Flashbots Protect oder MEV Blocker) verbirgt sie vor der öffentlichen Einsichtnahme. Designs auf Anwendungsebene wie Commit-Reveal-Schemata verschleiern Transaktionsdetails, bis die Reihenfolge festgelegt ist, während die Verwendung von zeitgewichteten Durchschnittspreisen (Time-Weighted Average Prices - TWAPs) für Orakel Manipulationsrisiken reduzieren kann.