Drücken Sie ESC, um zu schließen

ChatGPT Solidity Reentrancy Lücken: 3 Gefährliche Beispiele

Wisst ihr, als ich damals noch die Nächte auf Hackathons durchgemacht habe... ach Moment, welche Nächte eigentlich, wir haben damals doch meistens unter Server-Racks auf dem Boden geschlafen, dachte ich noch, dass grundlegende Schwachstellen in Smart Contracts von jedem x-beliebigen statischen Analysator erkannt werden können. Was soll daran schon schwer sein? Reentrancy-Angriffe sind der absolute Klassiker, darüber hat nun wirklich jeder schon geschrieben. Checks-Effects-Interactions-Pattern, diverse Modifier... Und dann kommt vor ein paar Monaten ein Junior in unser Team und verkündet stolz: «Schaut mal, ChatGPT hat mir den perfekten Staking-Contract generiert, ich hab's geprüft, der ist absolut sicher!»

Ich hätte fast meinen Kaffee über meine mechanische Tastatur gekippt.

Mal ganz ehrlich: Moderne LLMs schreiben verdammt sauberes Solidity. Die Syntax glänzt, die Imports sitzen, und OpenZeppelin ist nach den allerneuesten Best Practices eingebunden. Aber sobald es um die feingranulare State-Logik geht, fängt die KI an, mit einer felsfesten, dummen Überzeugung Löcher zu generieren, die bei Audits später Millionen von Dollar kosten. Schauen wir uns mal drei unoffensichtliche Fälle an, in denen ChatGPT komplett baden geht, während es euch versichert, dass euer Code jedes CertiK-Audit mit links besteht.

Fall 1: Asynchrones Staking mit Block-Rewards und verstecktem externem Aufruf

Hört zu, ihr bittet die KI, einen Vertrag zu schreiben, der Token basierend auf der Verweildauer in einem Pool verteilt. Was macht das Modell? Es knallt brav überall dort das nonReentrant-Pattern rein, wo es eine Token-Überweisung an den Nutzer sieht. Aber es vergisst dabei ein architektonisches Detail.

Schaut euch diesen Ausschnitt an, den ich nach der Aktion unseres Juniors aus einem echten Code-Review gezogen habe:

// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;
interface IERC20Mintable {
    function mint(address to, uint256 amount) external;
}
contract SecureStakingVault {
    IERC20Mintable public rewardToken;
    mapping(address => uint256) public balances;
    mapping(address => uint256) public lastUpdateBlock;
    
    uint256 public rewardRatePerBlock = 10 * 10**18;
    bool private locked;
    // Ist doch abgesichert, Mann! Was willst du noch, du Paranoiker?
    modifier noReentry() {
        require(!locked, "LOCKED");
        locked = true;
        _;
        locked = false;
    }
    constructor(address _token) {
        rewardToken = IERC20Mintable(_token);
    }
    function stake() external payable {
        require(msg.value > 0, "Zero stake");
        balances[msg.sender] += msg.value;
        lastUpdateBlock[msg.sender] = block.number;
    }
    // Das absolute Meisterwerk von ChatGPT, das so verdammt sicher aussieht
    function harvestAndUnstake(uint256 _amount) external noReentry {
        require(balances[msg.sender] >= _amount, "Insufficient balance");
        uint256 blocksPassed = block.number - lastUpdateBlock[msg.sender];
        uint256 reward = blocksPassed * rewardRatePerBlock * _amount / balances[msg.sender];
        // Achtung: State-Update findet VOR dem externen mint-Aufruf statt
        balances[msg.sender] -= _amount;
        lastUpdateBlock[msg.sender] = block.number;
        // Die Reward-Emission triggert einen Token-Constructor oder einen Hook, und zack... Überraschung!
        rewardToken.mint(msg.sender, reward);
        (bool success, ) = msg.sender.call{value: _amount}("");
        require(success, "Transfer failed");
    }
}

Wo ist hier der Haken? Das Modell ist steif und fest davon überzeugt, dass ein Angriff unmöglich ist, weil die Guthaben-Variablen ja vor rewardToken.mint() zurückgesetzt wurden. Nur blöd, dass rewardToken ein benutzerdefinierter ERC-20 ist, dessen Minting-Contract einen _beforeTokenTransfer-Hook oder einen ERC-777-ähnlichen Callback enthält (falls der Token komplexer oder proxybasiert ist). Im Moment des mint-Aufrufs springt die Ausführung in fremden Code ab, noch bevor das ETH beim Nutzer ankommt – allerdings während einer aktiven Session, in der der Pool-Zustand bereits einseitig manipuliert wurde und die Neuberechnung der Rewards für alle anderen User komplett zerschossen ist. Das ist Cross-Contract-Reentrancy der nächsten Generation, und Standard-Lehrbücher schweigen sich darüber fein aus.

Fall 2: Funktionsübergreifendes Hijacking über Liquiditäts-Oracles

Das Hintertückische an modernen Exploits ist, dass Reentrancy oft gar nicht innerhalb einer einzigen Funktion lauert, sondern zwischen zwei völlig unterschiedlichen Entry Points stattfindet, die sich ein gemeinsames Mapping teilen. Die KI liebt es, Logik in «saubere» Module zu zerlegen, vergisst dabei aber völlig die Reihenfolge, in der globale Invarianten verändert werden.

  • Schwachstellen-Parameter: Einschätzung des statischen Analysators
  • Reale Situation: Modifier
  • Vorhanden (nonReentrant): Völlig nutzlos gegen funktionsübergreifende Aufrufe
  • Ablaufreihenfolge: Checks-Effects lokal eingehalten
  • Globales Protokoll-Gleichgewicht zerstört: ChatGPTs Reaktion
  • «Der Code ist vollständig vor Reentrancy geschützt»: Lässt statusübergreifende Manipulationen zu

Fall 3: Verträge mit variabler Laufzeit und ERC-4626-Vaults

ChatGPT ist absolut besessen von den standardisierten OpenZeppelin-Templates für Yield-Vaults (ERC-4626). Es schnappt sich deren Standardbeispiele, wirft seine eigene Custom-Mathematik für die Anteilsberechnung rein und haut Code raus, der beim ersten Versuch compiliert. Nur leider ermöglicht die Funktion zur Konvertierung von Anteilen in Assets (convertToAssets) unter bestimmten Aufrufbedingungen über einen Zwischenvertrag einen mehrstufigen Kontrollfluss-Rücksprung während der Berechnung der virtuellen Liquidität.

Ich saß vor drei Wochen selbst bis mitten in der Nacht im Testnet und habe einen Bug gesucht, bei dem ein Simulations-Bot die gesamte Liquidität aus dem Pool exakt nach diesem Schema abgezogen hat. Das LLM riet mir allen Ernstes: «Verwenden Sie Multiplikator-Skalierung für mehr Präzision». Und was hatte man am Ende? Einen klassischen Read-Only-Reentrancy-Fehler, bei dem ein externes System den unbestätigten Zwischenzustand eines Vaults mitten während einer Einzahlung ausliest.

Tauchen wir weiter ab in dieses absurde Theater, das uns Sprachmodelle generieren, wenn wir sie bitten, einen „einfachen und sicheren Smart Contract“ zu schreiben.

Wisst ihr, was das Lustigste an der Arbeit als ehemaliger Security Auditor ist? Zu beobachten, wie Entwickler den Kommentaren von ChatGPT blind vertrauen. Die KI haut oben eine Zeile hin wie // Safe against reentrancy attacks thanks to Checks-Effects-Interactions — und zack, schaltet sich beim Dev der kritische Verstand ab. Autopilot an, Gehirn in den Standby, und schon gibt es im Mainnet ein astreines Feuerwerk.

Schauen wir uns zwei weitere eklatante Beispiele an, die KI hartnäckig für den Goldstandard der Sicherheit hält, obwohl sie in der Praxis sofort auseinandergenommen werden.

Fall Nr. 4: Mehrstufige Arbitrage mit Rückzahlung über Flash Loans

Das ist der absolute Klassiker, den man gerne auf Hackathons an Junior-Devs rausgibt: Einen Contract schreiben, der sich Flash-Crypto holt, sie über eine DEX jagt, den Gewinn einsackt, das geliehene Kapital zurückzahlt und die Provision im Liquiditätspool sichert. Was macht ChatGPT? Es platziert säuberlich require-Prüfungen für die Guthaben am Anfang und Ende der Transaktion, in der festen Überzeugung, dass das System unknackbar ist, wenn die Delta-Werte matchen.

Aber der Teufel steckt wie immer im Detail beim Aufruf der Callback-Funktion uniswapV2Call (oder eines entsprechenden Interfaces anderer Protokolle).

// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;
interface IUniswapV2Pair {
    function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external;
}
contract AIArbitrageBotHolder {
    address private immutable owner;
    bool private unlocked = true;
    // Die KI denkt, dieser Lock reicht aus, um die gesamte Logik abzusichern
    modifier lock() {
        require(unlocked, "LOCKED");
        unlocked = false;
        _;
        unlocked = true;
    }
    constructor() {
        owner = msg.sender;
    }
    // Die laut KI angeblich so „sichere“ Methode
    function executeFlashLoan(address _pair, uint256 _amountA, bytes calldata _data) external lock {
        // Liquiditätsanfrage an den Pool
        IUniswapV2Pair(_pair).swap(_amountA, 0, address(this), _data);
    }
    // Callback, an den der Pool die Kontrolle zurückgibt
    function uniswapV2Call(address sender, uint amount0, uint amount1, bytes calldata data) external {
        // Brutaler Fehler: Die Überprüfung von msg.sender wird von der KI für „sauberen“ Code oft weggelassen
        // Voila, hier kann jeder beliebige dubiose Contract aufschlagen und eine Reentrancy-Schleife triggern
        
        uint256 fee = amount0 * 3 / 997 + 1;
        uint256 repayment = amount0 + fee;
        // Von ChatGPT zusammengeschusterte Arbitrage-Logik
        _executeInternalArbitrage(amount0);
        // Rückzahlung der Schulden an den Pool
        // (Völlig veressend, dass der State externer Pools zu diesem Zeitpunkt noch nicht final gefixt ist)
        bool success = IERC20(msg.sender).transfer(msg.sender, repayment);
        require(success, "Repayment failed");
    }
    function _internalArbitrage(uint256 amount) internal {
        // Dummy für komplexe Swap-Logik
    }
}

Seht ihr die Lücke? Die KI knallt den Modifikator lock auf den externen Entry Point executeFlashLoan, vergisst aber komplett, dass die Callback-Funktion uniswapV2Call separat isoliert werden muss oder strikt validieren sollte, wer da aufruft (msg.sender). Ein Angreifer kann diesen Callback mitten in den Zwischenberechnungen direkt aus dem eigenen Contract heraus aufrufen – also zu einem Zeitpunkt, an dem die Balances des Wrapper-Contracts noch gar nicht konsistent sind – und das gesamte Collateral abräumen. ChatGPT wird euch darauf niemals hinweisen; es lächelt im Chat einfach mit ein paar Emojis und meint: „Code für den ERC-Standard optimiert.“

Fall Nr. 5: Optimierte ERC-721 Batch-Minting-Contracts mit Caching

Der moderne NFT-Markt verlangt nach billigem Gas. Das checkt die KI natürlich und fängt an, den Code zu „optimieren“, indem sie maßgeschneiderte Owner-Mapping-Strukturen und Cache-Counter direkt im Contract-Memory unterbringt.

  • Audit-Metrik: ChatGPT-Urteil — Tatsächlicher Schwachstellenstatus
  • Gas-Verbrauch: Extrem niedrig (Gas Optimized) — Erkauft durch Verletzung der State-Isolierung
  • Storage-Pattern: Nutzung lokaler Structs im Memory — Anfällig für Manipulationen über onERC721Received-Hooks
  • Stabilität: Besteht Standard-Hardhat-Tests — Crasht bei tiefen Reentrancy-Call-Stacks

Wenn ein Contract NFTs via safeMint raushaut, verlangt der Standard den Aufruf einer Bestätigungsfunktion beim empfangenden Smart Contract. Wenn ihr nun einen Batch-Mint fahrt (sagen wir 10 Token auf einen Schlag) und den Gesamtbilanzzähler nach dem Versand jedes einzelnen Tokens aktualisiert (oder in einer Schleife innerhalb eines externen Aufrufs), schnappt sich der Angreifer die Kontrolle ab der dritten Iteration. Zu diesem Zeitpunkt ist das interne Token-Array erst zur Hälfte gefüllt, während der Transaktionszähler noch glaubt, der Prozess fänge gerade erst an. Das Ergebnis: Index-Kollisionen und unendliche Asset-Prägungen aus dem Nichts.

Was sollen wir, ihr oder die gesamte Industrie jetzt damit anfangen?

Hört zu, ich schreibe selbst jeden Tag Code, aber generative Modellen in der Crypto-Welt blind zu vertrauen ist, wie Russisch Roulette zu spielen – nur mit vollautomatischem Magazin. Wenn ihr KI für die Protokollentwicklung nutzt, haltet euch an drei eiserne Regeln:

  • Glaubt niemals den Modifikatoren aus irgendwelchen Templates. Wenn die KI euch ein nonReentrant hinhaut, heißt das nur, dass sie das Vokabular kennt, aber absolut nicht den asynchronen Kontext eurer jeweiligen Blockchain versteht.
  • Checkt in Callback-Funktionen immer strikt das msg.sender. Interfaces wie uniswapV2Call, onERC721Received oder tokensReceived müssen dichter sein als ein Hochsicherheitsbunker.
  • Schreibt eure eigenen Fuzz-Tests in Foundry. Kein statischer Unit-Test findet Cross-Function-Reentrancies so zuverlässig wie ein paar Millionen randomisierte Invarianten-Durchläufe.

Während ich mich hier über diese Schwachstellen auslasse, lässt mich ein hartnäckiger Gedanke nicht los: Eigentlich haben wir den Markt selbst bequem gemacht. Wir haben uns daran gewöhnt, Routineaufgaben an die Code-Generierung zu delegieren, und dabei vergessen, dass die Blockchain keine Logik-Kompilierungsfehler verzeiht. In der traditionellen Web-Entwicklung kannst du einen Commit zurückrollen, einen Patch einspielen und dich bei den Nutzern für fünf Minuten Downtime entschuldigen. In der EVM-Umgebung bleibt dein Bug für immer in einem unveränderlichen Ledger verewigt – als Denkmal für menschliche Sorglosigkeit und den Triumph der algorithmischen Blindheit.

Bringن wir den Rest des Themas zu Ende und nehmen uns noch einen unkonventionellen Vektor vor, den ChatGPT mit beängstigender Konstanz generiert.

Fall Nr. 6: Liquiditäts-Aggregatoren und virtuelle Anteile in Stablecoin-Pools

Das ist der absolute letzte Schrei unter DeFi-Entwicklern – Custom-Swap-Pools mit dynamischer Token-Gewichtsberechnung auf Basis von Constant-Product-Curves zu erstellen. Neuronale Netze lieben diese Mathematik, weil im Netz terabyteinteig Open-Source-Code von Curve und Uniswap v2 herumliegt. Das Modell schnappt sich die Formel, passt sie an deine Anforderungen an, wirft eine addLiquidity-Funktion hinein und meldet stolz, dass das Produkt bereit für das Deployment ist.

Nur hat das Modell absolut keine Ahnung, wie die asynchrone Berechnung virtueller Salden funktioniert, wenn externe Token mit variablen Gebühren (wie USDT oder benutzerdefinierte deflationäre Token mit Transfersteuer) aufgerufen werden.

Schau dir mal dieses Pattern an, das die KI für absolut kugelsicher hält:

// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;
interface ITokenWithFee {
    function transferFrom(address sender, address recipient, amount) external returns (bool);
    function balanceOf(address account) external view returns (uint256);
}
contract AIWarpPool {
    mapping(address => uint256) public userShares;
    uint256 public totalShares;
    
    // Die KI denkt, wenn wir erst das Guthaben berechnen und dann in das Mapping schreiben, sind wir sicher
    function deposit(address token, uint256 amount) external {
        uint256 balanceBefore = ITokenWithFee(token).balanceOf(address(this));
        
        // Externer Token-Transferaufruf
        bool success = ITokenWithFee(token).transferFrom(msg.sender, address(this), amount);
        require(success, "Transfer failed");
        
        uint256 balanceAfter = ITokenWithFee(token).balanceOf(address(this));
        uint256 realReceived = balanceAfter - balanceBefore;
        // Ausgabe von Anteilen basierend auf den tatsächlich eingegangenen Mitteln
        uint256 shares = totalShares == 0 ? realReceived : (realReceived * totalShares) / balanceBefore;
        
        userShares[msg.sender] += shares;
        totalShares += shares;
    }
    function withdraw(uint256 shares) external {
        require(userShares[msg.sender] >= shares, "Not enough shares");
        
        uint256 amountToReturn = (shares * ITokenWithFee(msg.sender).balanceOf(address(this))) / totalShares;
        
        // Erst den State aktualisieren...
        userShares[msg.sender] -= shares;
        totalShares -= shares;
        // ...und dann die Mittel herausgeben. Klassisches Checks-Effects-Interactions!
        // Aber Moment mal...
        (bool success, ) = msg.sender.call{value: 0}(""); // Bedingter externer Token-Transferaufruf
        require(success, "Withdraw failed");
    }
}

Siehst du die Falle? Das neuronale Netz hat die Checks-Effects-Interactions-Reihenfolge in der Funktion withdraw brav eingehalten. Aber es hat völlig übersehen, dass die Berechnung von amountToReturn vom aktuellen balanceOf(address(this)) zum Zeitpunkt des Aufrufs abhängt. Wenn ein Angreifer einen Wrapper-Contract erstellt, der beim Erhalt der Kontrolle (oder über einen Token-Callback) einen Reentrancy-Angriff auf withdraw auslöst, bevor sich das globale totalShares in einem anderen verbundenen Pool oder über einen Cross-Contract-Oracle-Aufruf ändert, berechnet er die Proportion anhand eines veralteten Liquiditätswerts.

Die finale Checkliste zur Überprüfung deines KI-generierten Codes

Hör auf, immer wieder in dieselbe Falle zu tappen und darauf zu hoffen, dass ein neuronales Netz schlauer ist als du, wenn es um verteilte Systemarchitektur geht. Bevor du generierten Code in die Production oder auch nur in ein Testnet mit echtem Geld schickst, geh diese Liste durch:

  • Isoliere jegliche Callback-Funktionen so, als würde in ihnen ein Hacker mit einem fertigen Exploit lauern.
  • Prüfe jeden externen Aufruf darauf, ob er dem Contract die Kontrolle in einem Moment zurückgeben kann, in dem globale Invarianten noch nicht konsistent sind.
  • Vergiss den blinden Glauben an Modifier – sie schützen lediglich vor direkter Reentrancy in dieselbe Funktion, sind aber machtlos gegen komplexe Cross-Contract-Ketten.

FAQ

Das Modell stützt sich auf statisches Musterabgleichen und kann asynchrone Statusänderungen über mehrere Smart Contracts hinweg nicht verfolgen, weshalb globale Invarianten vor externen Aufrufen inkonsistent bleiben.

Entwickler müssen das Checks-Effects-Interactions-Muster strikt anwenden, interne Callback-Funktionen vollständig isolieren und automatisiertes Invariant-Fuzzing mit Foundry einsetzen, statt blind auf Modifier wie nonReentrant zu vertrauen.

Große Sprachmodelle vernachlässigen virtuelle Saldoberechnungen und vorzeitige Statusaktualisierungen, wodurch bösartige Verträge während temporärer Token-Callbacks in die Kernlogik zurückkehren können.
Oleg Filatov

As the Chief Technology Officer at EXMON Exchange, I focus on building secure, scalable crypto infrastructure and developing systems that protect user assets and privacy.

With over 15 years in cybersecurity, blockchain, and DevOps, I specialize in smart contract analysis, threat modeling, and secure system architecture.

At EXMON Academy, I share practical insights from real-world...

...

Diskussion beitreten

Ihre E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind markiert *