Web3 Signing Workflow Compromise & Safe Delegatecall Proxy Takeover
Tip
Apprenez et pratiquez le hacking AWS :
HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP :HackTricks Training GCP Red Team Expert (GRTE)
Apprenez et pratiquez le hacking Azure :
HackTricks Training Azure Red Team Expert (AzRTE)
Soutenir HackTricks
- Vérifiez les plans d’abonnement !
- Rejoignez le 💬 groupe Discord ou le groupe telegram ou suivez-nous sur Twitter 🐦 @hacktricks_live.
- Partagez des astuces de hacking en soumettant des PR au HackTricks et HackTricks Cloud dépôts github.
Vue d’ensemble
Une chaîne de vol visant des cold-wallets a combiné une supply-chain compromise de l’UI web de Safe{Wallet} avec une primitive on-chain delegatecall qui a écrasé le pointeur d’implémentation d’un proxy (slot 0). Les points clés sont :
- Si une dApp peut injecter du code dans le chemin de signature, elle peut faire en sorte qu’un signataire produise une EIP-712 signature valide sur des champs choisis par l’attaquant tout en restaurant les données UI originales pour que les autres signataires ne s’en aperçoivent pas.
- Les proxys Safe stockent
masterCopy(implementation) à storage slot 0. Un delegatecall vers un contrat qui écrit dans le slot 0 « met à niveau » le Safe avec la logique de l’attaquant, donnant le contrôle complet du wallet.
Off-chain : Mutation ciblée de la signature dans Safe
Un bundle Safe falsifié (_app-*.js) ciblait sélectivement des adresses Safe et des adresses de signataires spécifiques. La logique injectée s’exécutait juste avant l’appel de signature :
// 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;
}
Attack properties
- Context-gated: des listes blanches codées en dur pour les Safes/signers des victimes ont réduit le bruit et la détection.
- Last-moment mutation: les champs (
to,data,operation, gas) ont été écrasés immédiatement avantsignTransaction, puis rétablis, de sorte que les payloads des proposals dans l’UI semblaient bénins alors que les signatures correspondaient au payload de l’attaquant. - EIP-712 opacity: les wallets affichaient des données structurées mais ne décodaient pas le calldata imbriqué ni ne mettaient en évidence
operation = delegatecall, rendant le message muté effectivement signé à l’aveugle.
Gateway validation relevance
Les proposals Safe sont soumises au Safe Client Gateway. Avant les contrôles renforcés, le gateway pouvait accepter une proposal où safeTxHash/signature correspondait à des champs différents du corps JSON si l’UI les réécrivait après signature. Après l’incident, le gateway rejette désormais les proposals dont le hash/la signature ne correspondent pas à la transaction soumise. Une vérification similaire côté serveur du hash devrait être appliquée à toute API d’orchestration de signature.
On-chain: Delegatecall proxy takeover via slot collision
Les Safe proxies conservent masterCopy au storage slot 0 et délèguent toute la logique vers celui-ci. Parce que Safe supporte operation = 1 (delegatecall), toute transaction signée peut pointer vers un contrat arbitraire et exécuter son code dans le contexte de stockage du proxy.
Un contrat attaquant a imité un ERC-20 transfer(address,uint256) mais a plutôt écrit _to dans le slot 0:
// Decompiler view (storage slot 0 write)
uint256 stor0; // slot 0
function transfer(address _to, uint256 _value) external {
stor0 = uint256(uint160(_to));
}
Chemin d’exécution :
- Les victimes signent
execTransactionavecoperation = delegatecall,to = attackerContract,data = transfer(newImpl, 0). - Safe
masterCopyvalide les signatures sur ces paramètres. - Le proxy effectue un
delegatecallversattackerContract; le corps detransferécrit dans le slot 0. - Le slot 0 (
masterCopy) pointe désormais vers une logique contrôlée par l’attaquant → prise de contrôle complète du portefeuille et vidage des fonds.
Détection et checklist de durcissement
- Intégrité de l’UI : pin des assets JS / SRI ; surveiller les diffs de bundle ; considérer l’UI de signature comme faisant partie de la frontière de confiance.
- Validation au moment de la signature : hardware wallets avec EIP-712 clear-signing ; afficher explicitement
operationet décoder la calldata imbriquée. Rejeter la signature lorsqueoperation = 1sauf si la politique l’autorise. - Vérifications de hash côté serveur : les gateways/services qui relaient des propositions doivent recalculer
safeTxHashet valider que les signatures correspondent aux champs soumis. - Politiques/listes blanches : règles de prévalidation pour
to, les selectors, les types d’actifs, et interdiredelegatecallsauf pour des flux vérifiés. Exiger un service de politique interne avant de diffuser des transactions entièrement signées. - Conception des contrats : éviter d’exposer des
delegatecallarbitraires dans les multisig/treasury wallets sauf si strictement nécessaire. Placer les pointeurs d’upgrade loin du slot 0 ou les protéger avec une logique d’upgrade explicite et un contrôle d’accès. - Monitoring : alerter sur les exécutions de
delegatecallprovenant de wallets détenant des fonds de trésorerie, et sur les propositions qui changentoperationpar rapport aux schémascalltypiques.
References
Tip
Apprenez et pratiquez le hacking AWS :
HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP :HackTricks Training GCP Red Team Expert (GRTE)
Apprenez et pratiquez le hacking Azure :
HackTricks Training Azure Red Team Expert (AzRTE)
Soutenir HackTricks
- Vérifiez les plans d’abonnement !
- Rejoignez le 💬 groupe Discord ou le groupe telegram ou suivez-nous sur Twitter 🐦 @hacktricks_live.
- Partagez des astuces de hacking en soumettant des PR au HackTricks et HackTricks Cloud dépôts github.
HackTricks

