Hintergrund

Am 18. Juni 2026 wurde der RollupProcessor-Vertrag des Ethereum Layer2-Pirvatsprotokolls Aztec Connect angegriffen. Der Angreifer nutzte die Escape Hatch-Mechanik, um den Vertrauensgrenzenfehler auszunutzen, dass auf der Solidity-Ebene die unabhängige Zuordnung von Geldern und die Prüfung der Abhebungsgrenzen fehlten, sowie die fehlenden Schaltungsbeschränkungen. Er reichte einen von TurboVerifier akzeptierten Escape Hatch-Beweis ein und zog direkt 1.158 ETH (ca. 2,06 Millionen USD) aus dem Vertragsguthaben. Zudem wurden über dasselbe Mechanismus 150.000 DAI und 0,47 renBTC gestohlen, was insgesamt einem Verlust von etwa 2,22 Millionen USD entspricht.

Angriffsblick

Angriffstransaktion:

Ursache der Schwachstelle

Fehlende Vertrauensgrenze von escapeHatch()

Die öffentliche Einstiegfunktion escapeHatch() des RollupProcessors prüft nur, ob der Escape-Hatch-Status aktiviert ist, und geht dann direkt in processRollupProof() über; es findet keinerlei Prüfung der Aufruferberechtigungen statt:

Im Gegensatz dazu prüft der normale Rollup-Verarbeitungsweg processRollup() die Autorisierung von rollupProviders[provider] und validiert die provider signature. In diesem Angriff wird jedoch escapeHatch() aufgerufen, mit signatures = 0x und viewingKeys = 0x, wodurch der gesamte provider-Autorisierungsprozess vollständig umgangen wird.

verifyProofAndUpdateState() vertraut blind auf den Verifier

Nach dem Eintritt in processRollupProof() ruft der Vertrag TurboVerifier.verify(proofData, 0) auf:

TurboVerifier selbst arbeitet korrekt – er führt die mathematische Verifikation des Plonk-Beweises durch. Das eigentliche Problem liegt darin, dass der RollupProcessor „mathematisch erfolgreiche Verifikation“ mit „geschäftliche Legalität“ gleichsetzt und nicht noch eine zusätzliche, unabhängige Prüfung in der Solidity-Schicht vornimmt.

processDepositsAndWithdrawals() führt Auszahlungen bedingungslos aus

Kern-Execution-Logik nach erfolgreicher Verifier-Rückkehr:

Letzte Funktion zum Abziehen der Mittel:

Im zkSNARK-Schaltkreis fehlen Gleichungs-Constraint-Tore (die eigentliche Ursache)

Im Aztec-Join-Split-Beweisstrom wird old_data_root in zwei unabhängige Pfade aufgeteilt, die jeweils verwendet werden:

  • Interner Zeuge A: Übergabe des Join-Split-Teil-Schaltkreises zur Validierung der Merkle-Mitgliedschaft privater Notizen

  • Öffentliche Eingabe B: als öffentliche Eingabe für die Solidity-Schicht offengelegt, damit validateMerkleRoots() mit dem On-Chain-dataRoot abgeglichen werden kann

Im Schaltkreis fehlen die Nebenbedingung A == B, wodurch beide unabhängig belegt werden können:

  • A kann eine vom Angreifer konstruierte gefälschte Merkle-Baumwurzel sein (im Baum befindet sich eine Notiz über beliebige Beträge)

  • B kann ein echter On-Chain-dataRoot sein (damit die Solidity-Prüfung besteht)

Da das Constraint-System A ≠ B zulässt, bleibt der gesamte zkSNARK-Proof weiterhin gültig.

Angriffsablauf

Vorbereitungsphase: Fälschen des Merkle-Baums und des Proof

Der Angreifer baut lokal einen gefälschten data-Merkle-Baum, in den er eine private Notiz einfügt:

  • Wert: 1158 ETH (der Vertrag hielt zu diesem Zeitpunkt etwa 1158,7598 ETH)

  • Eigentümer: Angreifer besitzt den entsprechenden privaten Schlüssel

Der Angreifer konstruiert den zkSNARK-Proof:

  • Interner Zeuge A (old_data_root) = gefälschte Baumwurzel

  • Öffentliche Eingabe B (old_data_root) = On-Chain-tatsächliches dataRoot = 0x184bea7d9493cd9a5efb6b679d04066a8c92a34ac8ec150e9635133c6010977b

Erste Phase: Konstruktion der Angriffstransaktion

Der Angreifer EOA 0x6952d9246e9aFE8B887B2877225163436F78E97F startet direkt einen Top-Level-Call an den RollupProcessor (0x737901bea3eeb88459df9ef1be8ff3ae1b42a2ba):

  • Funktionssignatur: escapeHatch(bytes,bytes,bytes)

  • msg.value = 0

  • signatures = 0x

  • viewingKeys = 0x

Die vom Angreifer konstruierte proofData enthält die entscheidenden öffentlichen Inputs:

Zweite Phase: Umgehung der Berechtigungsprüfung

escapeHatch() prüft nur, ob getEscapeHatchStatus() true zurückgibt, und ruft dann direkt processRollupProof() auf. Dieser Pfad führt keine Autorisierungsprüfung von rollupProviders[provider] aus wie im normalen processRollup() und validiert auch nicht die provider signature. Zwischen der Identität des Aufrufers und dem Auszahlungs-Empfänger besteht keinerlei Bindung.

Dritte Phase: Mathematische Verifikation durch den Verifier

Der RollupProcessor ruft TurboVerifier (0x48cb7ba00d087541dc8e2b3738f80fdd1fee8ce8) per STATICCALL auf:

Da rollup_size = 0 ist, führt TurboVerifier durch Auswahl von EscapeHatchVk über VerificationKeys.getKeyById(0) (vk.num_inputs = 26) die mathematische Plonk-Proof-Validierung durch. Der Verifier prüft nur die mathematische Konsistenz zwischen proof und public inputs, liest aber keinen RollupProcessor-Status aus und prüft nicht, wem die Salden zugeordnet sind.

Da der Verifier in dieser Transaktion erfolgreich zurückkehrt, ist der escape-hatch-Proof, den der Angreifer eingesendet hat, mathematisch korrekt.

Vierte Phase: Ausführen der Auszahlung

Nachdem der Verifier erfolgreich zurückkehrt, aktualisiert der RollupProcessor den Rollup-Status:

Danach geht es in processDepositsAndWithdrawals() über; im Escape-Hatch-Modus wird bei rollupSize == 0 weiterhin jede einzelne inner transaction mit 1 Buchung behandelt. Nach dem Parsen von proofData wird festgestellt:

Direkter Aufruf von withdraw(1158 ETH, Angreiferadresse, 0); das verschiebt die Mittel über receiverAddress.call{value: 1158 ETH}("") aus dem Vertrag.

Fünfte Phase: Angriff nach demselben Muster auf DAI und renBTC

Der Angreifer konstruierte in derselben Zeitspanne denselben Schwachstellenpfad und erstellte jeweils escape-hatch-Proofs, die auf ERC20-Assets abzielen (assetId ≠ 0), um 150.000 DAI und 0,47 renBTC aus dem RollupProcessor zu stehlen. Im withdraw()-Funktionszweig assetId ≠ 0 wird die ERC20-Übertragung per transfer() ausgeführt.

Nettoertrag der drei Angriffe:

Tracking der Gelder

Analyse des Angreifers EOA 0x6952d9246e9aFE8B887B2877225163436F78E97F mit dem langsamen Nebel MistTrack-Anti-Geldwäsche-Tracking-System:

Quelle der Mittel: die anfänglichen Gas-Kosten, die von der Adresse verwendet wurden, die der Angreifer beim Erstellen verwendet hat, stammen aus 0x963737c550e70ffe4d59464542a28604edb2ef9a (Entitätsadresse von unionchain.ai). Diese Adresse wurde erstmals am 18. Juni 2026 um 02:21:11 UTC aktiv, genau zum Zeitpunkt, als die Angriffshandlung startete – es handelt sich um eine Einmaladresse, die eigens für diesen Angriff erstellt wurde.

Ziel der Gelder: Die aktuell gestohlene Verteilung lautet wie folgt:

  • Der 802-ETH-Token ist noch immer in der Hauptadresse des Angreifers 0x6952d9246e9aFE8B887B2877225163436F78E97F vorhanden; außerdem wurden 150.000 DAI und 0,47 renBTC ebenfalls noch nicht übertragen

  • 300 ETH wurden an 0x15930a0fef3421f48c6553b5691682cc1b22edb3 überwiesen (MistTrack markiert als mit Aztec Exploiter verknüpfte bösartige Adresse)

  • Etwa 56 ETH wurden an eine weitere bösartige Adresse überwiesen (ebenfalls als mit Aztec Exploiter verknüpft markiert)

  • AML-Risikoscore 100/100 (Severe) für 0x33d6a0d9bc210e823e043d604179cd844eb467df; betrifft ebenfalls einen Aztec-Exploiter-Vorfall

Zusammenfassung

Die zentrale Lehre aus diesem Angriff ist: „Mathematische Korrektheit“ des zkSNARK-Schaltkreises ≠ „Geschäftliche Sicherheit“. Der Verifizierer kann nur beweisen: „Du weißt einen Zeugen, der die Constraints erfüllt“. Wenn die Constraints selbst falsch sind (oder fehlen), wird das Beweissystem zu einer legitimen Fälschungsmaschine. Das SlowMist-Sicherheitsteam empfiehlt dem Projektbetreiber, alle Pfade von Zero-Knowledge-Proof-Constraints einer speziellen Prüfung zu unterziehen, um die Sicherheit des Schaltkreises sicherzustellen.

Dieser Artikel wurde vom SlowMist Threat Intelligence Team gemeinsam mit dem MistEye Threat Intelligence System, der MistTrack-Tracking-Plattform und dem SlowMist Agent AI-gestützten Analyse-Tool erstellt. Bei Fragen wendet euch bitte gerne an uns – Rückmeldungen sind willkommen.