Web3 Signing Workflow Compromise & Safe Delegatecall Proxy Takeover
Tip
Lernen & üben Sie AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Lernen & üben Sie GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Lernen & üben Sie Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Unterstützen Sie HackTricks
- Überprüfen Sie die Abonnementpläne!
- Treten Sie der 💬 Discord-Gruppe oder der Telegram-Gruppe bei oder folgen Sie uns auf Twitter 🐦 @hacktricks_live.
- Teilen Sie Hacking-Tricks, indem Sie PRs an die HackTricks und HackTricks Cloud GitHub-Repos senden.
Übersicht
Eine Cold-Wallet-Diebstahlkette kombinierte eine Supply-Chain-Kompromittierung der Safe{Wallet} Web-UI mit einer on-chain delegatecall-Primitive, die den Implementierungszeiger (Slot 0) eines Proxys überschrieb. Die wichtigsten Erkenntnisse sind:
- Wenn eine dApp Code in den Signing-Pfad injizieren kann, kann sie einen Signer dazu bringen, eine gültige EIP-712-Signatur über vom Angreifer gewählte Felder zu erzeugen, während die ursprünglichen UI-Daten wiederhergestellt werden, sodass andere Signer nichts bemerken.
- Safe-Proxys speichern
masterCopy(Implementation) in storage slot 0. Ein delegatecall zu einem Vertrag, der in Slot 0 schreibt, “upgradet” den Safe effektiv auf Angreifer-Logik und verschafft so vollständige Kontrolle über die Wallet.
Off-chain: Targeted signing mutation in Safe
Ein manipuliertes Safe-Bundle (_app-*.js) griff selektiv bestimmte Safe- + Signer-Adressen an. Die injizierte Logik wurde unmittelbar vor dem Signing-Aufruf ausgeführt:
// Pseudocode of the malicious flow
orig = structuredClone(tx.data);
if (isVictimSafe && isVictimSigner && tx.data.operation === 0) {
tx.data.to = attackerContract;
tx.data.data = "0xa9059cbb..."; // ERC-20 transfer selector
tx.data.operation = 1; // delegatecall
tx.data.value = 0;
tx.data.safeTxGas = 45746;
const sig = await sdk.signTransaction(tx, safeVersion);
sig.data = orig; // restore original before submission
tx.data = orig;
return sig;
}
Angriffseigenschaften
- Context-gated: hartkodierte Allowlists für Opfer-Safes/Signierer verhinderten „Rauschen“ und reduzierten die Entdeckungswahrscheinlichkeit.
- Last-moment mutation: Felder (
to,data,operation, gas) wurden unmittelbar vorsignTransactionüberschrieben und danach wiederhergestellt, sodass die Proposal-Payloads in der UI harmlos wirkten, während die Signaturen zum Angreifer-Payload passten. - EIP-712-Intransparenz: Wallets zeigten strukturierte Daten, dekodierten aber keine verschachtelte calldata und hoben
operation = delegatecallnicht hervor, wodurch die mutierte Nachricht effektiv blind-gesigned wurde.
Relevanz der Gateway-Validierung
Safe proposals werden an das Safe Client Gateway übermittelt. Vor den verstärkten Prüfungen konnte das Gateway eine Proposal akzeptieren, bei dem safeTxHash/Signatur anderen Feldern als dem JSON-Body entsprachen, falls die UI diese nach dem Signieren umschrieb. Nach dem Vorfall lehnt das Gateway nun Proposals ab, deren Hash/Signatur nicht mit der eingereichten Transaktion übereinstimmen. Eine ähnliche serverseitige Hash-Verifikation sollte für jede Signing-Orchestrierungs-API durchgesetzt werden.
On-chain: Delegatecall Proxy-Übernahme durch Slot-Kollision
Safe-Proxies halten masterCopy in storage slot 0 und delegieren jegliche Logik dorthin. Da Safe operation = 1 (delegatecall) unterstützt, kann jede signierte Transaktion auf einen beliebigen Vertrag zeigen und dessen Code im Speicher-Kontext des Proxys ausführen.
Ein Angreifervertrag imitierte ein ERC-20 transfer(address,uint256), schrieb aber stattdessen _to in Slot 0:
// Decompiler view (storage slot 0 write)
uint256 stor0; // slot 0
function transfer(address _to, uint256 _value) external {
stor0 = uint256(uint160(_to));
}
Ausführungspfad:
- Opfer signieren
execTransactionmitoperation = delegatecall,to = attackerContract,data = transfer(newImpl, 0). - Safe
masterCopyvalidiert Signaturen über diese Parameter. - Proxy führt
delegatecallinattackerContractaus; dertransfer-Body schreibt in slot 0. - Slot 0 (
masterCopy) zeigt nun auf angreiferkontrollierte Logik → vollständige Übernahme der Wallet und Abfluss der Gelder.
Erkennung & Härtungs-Checkliste
- UI-Integrität: JS-Assets pinnen / SRI; Bundle-Diffs überwachen; die Signing-UI als Teil der Vertrauensgrenze behandeln.
- Sign-time validation: Hardware-Wallets mit EIP-712 clear-signing;
operationexplizit darstellen und verschachtelte calldata decodieren. Signing ablehnen, wennoperation = 1, sofern die Richtlinie dies nicht erlaubt. - Server-side hash checks: Gateways/Services, die Vorschläge weiterleiten, müssen
safeTxHashneu berechnen und prüfen, dass die Signaturen mit den eingereichten Feldern übereinstimmen. - Policy/allowlists: Preflight-Regeln für
to, Selektoren, Asset-Typen unddelegatecallverbieten, außer für geprüfte Flows. Vor dem Broadcast vollständig signierter Transaktionen einen internen Policy-Service verlangen. - Contract design: Arbiträre
delegatecallin multisig/treasury wallets vermeiden, sofern nicht strikt erforderlich. Upgrade-Pointer nicht in slot 0 platzieren oder mit expliziter Upgrade-Logik und Access Control absichern. - Monitoring: Alerts bei
delegatecall-Ausführungen von Wallets mit Treasury-Fonds sowie bei Vorschlägen, dieoperationvon typischencall-Mustern abändern.
Referenzen
Tip
Lernen & üben Sie AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Lernen & üben Sie GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Lernen & üben Sie Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Unterstützen Sie HackTricks
- Überprüfen Sie die Abonnementpläne!
- Treten Sie der 💬 Discord-Gruppe oder der Telegram-Gruppe bei oder folgen Sie uns auf Twitter 🐦 @hacktricks_live.
- Teilen Sie Hacking-Tricks, indem Sie PRs an die HackTricks und HackTricks Cloud GitHub-Repos senden.
HackTricks

