Auteur : Xiao Bai
Éditeur : Liz
Vue d'ensemble
Dans l'écosystème Ethereum, le mécanisme de génération déterministe d'adresses de contrat offre commodité aux développeurs, mais introduit également de nouvelles surfaces d'attaque. Dans cet épisode, nous analyserons les méthodes d'attaque et les stratégies de défense utilisant les codes d'opération CREATE et CREATE2 pour déployer différents contrats à la même adresse à différents moments. Pour les articles précédents sur l'audit de sécurité des contrats intelligents, consultez la collection.
Connaissances préalables
Commençons par comprendre les deux règles de génération d'adresses Ethereum :
1. CREATE
CREATE est le code d'opération natif utilisé pour déployer dynamiquement des contrats intelligents dans la machine virtuelle Ethereum (EVM). Depuis le bloc de genèse d'Ethereum, tous les déploiements de contrats dépendent de ce mécanisme. Sa caractéristique principale est que la génération d'adresse dépend du nonce du compte du déployeur, donc l'adresse est non déterministe (impossible de la prédire avec précision avant le déploiement).
L'adresse du contrat générée par CREATE dépend de l'adresse du déployeur et du nonce de cette adresse :

2. CREATE2
CREATE2 est un nouveau code d'opération introduit dans le hard fork Constantinople d'Ethereum (février 2019) pour la création de contrats (EIP-1014). Contrairement au CREATE traditionnel, avant le déploiement du contrat, les participants hors chaîne peuvent pré-calculer l'adresse du contrat, rendant possibles les interactions hors chaîne (comme les canaux d'état) et des architectures de contrats complexes.
L'adresse du contrat générée par CREATE2 est déterminée par les quatre paramètres suivants :

À ce stade, je suis sûr que vous avez compris les deux méthodes de génération d'adresses de contrat Ethereum. Les lecteurs attentifs peuvent se demander ce qui se passe si l'adresse du contrat calculée existe déjà ? Pas d'inquiétude, dans Ethereum, que ce soit par CREATE ou CREATE2, tant qu'une adresse existe déjà sur la chaîne (qu'il s'agisse d'un compte externe EOA ou d'un compte de contrat), l'EVM rejettera la demande de création de contrat. Voici deux scénarios de conflit d'adresses :
1. L'adresse cible est un compte externe (EOA)
Règle : si l'adresse cible est un EOA existant (par exemple, une adresse de portefeuille utilisateur), l'EVM rejettera la demande de déploiement de contrat.
Résultat : la transaction échoue, le Gas est consommé, le contrat ne sera pas créé et aucune donnée à cette adresse ne sera écrasée.
2. L'adresse cible est un compte de contrat
Règle : si l'adresse cible est un compte de contrat déjà déployé, l'EVM rejettera également la demande de déploiement.
Résultat : la transaction échoue, le Gas est consommé, le code et les données de stockage du contrat d'origine restent inchangés.
Bien sûr, il y a des exceptions. Si l'adresse cible est un contrat et a été détruite par selfdestruct, alors il est possible de déployer un nouveau contrat à cette adresse.
À ce stade, les diverses caractéristiques des codes d'opération CREATE et CREATE2 ont été présentées. Voyons maintenant comment utiliser les caractéristiques de ces deux codes d'opération pour exécuter une série de combinaisons et réaliser une attaque de contrat.
Exemple de vulnérabilité

Analyse des vulnérabilités
Ce contrat DAO met en œuvre un mécanisme de gouvernance de base : le propriétaire approuve les propositions via la fonction approve et enregistre l'adresse du contrat de proposition dans le tableau Proposals. Tout utilisateur peut ensuite exécuter les propositions approuvées via la fonction execute. Bien que le contrôle des droits semble strict (seul le propriétaire peut approuver les propositions), associé à une vérification d'exécution (les propositions doivent être approuvées et non exécutées), il existe en réalité une vulnérabilité logique cachée : l'adresse de proposition approuvée peut pointer vers un code de contrat complètement différent lors de l'exécution. L'attaquant peut procéder à l'attaque en trois étapes comme suit :
1. Déployer un contrat normal, obtenir une autorisation
L'attaquant déploie d'abord un contrat A contenant une fonction executeProposal() inoffensive, et l'adresse est ajoutée à la liste des propositions après approbation par le propriétaire.
2. Détruire le contrat d'origine, prendre possession de l'adresse
Le contrat A exécute l'opération de destruction automatique (selfdestruct) pour vider le code, puis l'attaquant utilise le code d'opération CREATE2 pour déployer le contrat malveillant B à la même adresse (contenant la logique dangereuse de executeProposal()).
3. Déclencher l'exécution, prendre le contrôle
Lorsque l'utilisateur appelle execute, le contrat exécute le code du nouveau contrat malveillant B via delegatecall. Comme delegatecall conserve le contexte du contrat actuel, l'attaquant peut modifier l'état du contrat DAO (comme changer le propriétaire) ou transférer des actifs.
Voyons maintenant le processus d'attaque en combinant le contrat d'attaque.
Contrat d'attaque

Le processus d'attaque est le suivant :
1. Alice a déployé le contrat DAO.
2. Evil déploie le contrat DeployerDeployer, dont l'adresse est DD.
3. Evil appelle DD.deploy(), utilisant CREATE2 pour déployer Deployer à l'adresse D (sel fixe).
4. Appeler D.deployProposal(), créer l'adresse du contrat Proposal P, à ce moment D a un nonce de 0.
5. Alice approuve l'adresse P.
6. L'attaquant appelle D.kill(), détruit D, à ce moment, le compte de l'adresse D est vidé, y compris le nonce qui est également réinitialisé à 0.
7. L'attaquant appelle à nouveau DD.deploy(), redéploye le Deployer à l'adresse D (mêmes paramètres CREATE2).
8. Appeler D.deployAttack(), car à ce moment D a son nonce réinitialisé à 0 et l'adresse de D n'a pas changé, l'adresse du contrat Attack créé est donc identique à l'adresse du contrat Proposal P précédemment créé.
9. À ce stade, le tableau proposals du DAO contient une proposition pointant vers l'adresse P, mais maintenant l'adresse P a été redéployée en tant que contrat d'attaque. Par conséquent, lorsque execute() est appelé, cela exécutera executeProposal() de Attack, modifiant finalement le propriétaire du DAO en msg.sender, c'est-à-dire le contrat Attack.
Le principe de l'attaque peut être résumé comme suit : l'attaquant utilise la capacité de CREATE2 à redéployer un contrat à la même adresse. D'abord, il déploie un contrat Proposal légitime et obtient l'approbation du DAO. Ensuite, il détruit le contrat Deployer, redéploie le Deployer à la même adresse, réinitialise le nonce de cette adresse, et réussit finalement à déployer un contrat malveillant à la même adresse que la précédente Proposition. Comme l'adresse de proposition stockée dans le DAO pointe maintenant vers le contrat malveillant, lorsque la proposition est exécutée, le code malveillant s'exécute dans le contexte du DAO, modifiant avec succès le propriétaire en attaquant.
Suggestions de correction
En tant que développeur :
Lors de l'approbation d'une proposition, il ne suffit pas d'enregistrer l'adresse, mais aussi de mémoriser le hachage de code de cette adresse et de vérifier la cohérence du hachage lors de l'exécution.
Éviter d'utiliser delegatecall pour appeler des contrats externes, à moins d'avoir des mesures de sécurité adéquates.
Considérer la possibilité de redéployer un contrat à la même adresse après sa destruction, lors d'appels externes, vérifier si le contrat externe est fiable. Si vous devez appeler un contrat externe inconnu, il est conseillé de vérifier si le contrat contient des mécanismes de protection contre la destruction.
En tant qu'auditeur :
Identifier si le contrat des appels externes contient une fonction de destruction automatique (selfdestruct). Si c'est le cas, il faut être vigilant face au risque d'attaques où un contrat externe détruit son code et déploie un code malveillant à la même adresse.
Vérifier si la méthode de déploiement du contrat utilise CREATE2, s'assurer que la valeur de sel (salt) utilisée pour générer l'adresse du contrat est suffisamment aléatoire, pour éviter qu'un attaquant ne déploie à l'avance un contrat à cette adresse grâce à une prédiction de l'adresse du contrat.
Vérifier si le DAO vérifie la cohérence du code de l'adresse cible lors de l'exécution de la proposition, par exemple en comparant le hachage du code actuel avec le hachage au moment de l'approbation.
Analyser avec prudence les scénarios d'utilisation de delegatecall, confirmer si l'adresse cible est fiable et s'il existe un risque d'appel d'une adresse arbitraire.
Examiner la gestion du cycle de vie des propositions, par exemple s'il existe un mécanisme pour empêcher les propositions d'être modifiées ou remplacées, comme verrouiller l'état de l'adresse cible après approbation.
