Sécurité de contrat intelligent
Dernière mise à jour de la page : 26 février 2026
Les contrats intelligents sont extrêmement flexibles et capables de contrôler de grandes quantités de valeur et de données, tout en exécutant une logique immuable basée sur le code déployé sur la blockchain. Cela a créé un écosystème dynamique d’applications sans tiers de confiance et décentralisées qui offrent de nombreux avantages par rapport aux systèmes existants. Ils représentent également des opportunités pour les attaquants qui cherchent à tirer profit de vulnérabilités dans les contrats intelligents.
Les blockchains publiques, comme Ethereum, compliquent encore davantage la question de la sécurisation des contrats intelligents. Le code de contrat déployé ne peut généralement pas être modifié pour corriger des failles de sécurité, tandis que les actifs volés sur des contrats intelligents sont extrêmement difficiles à suivre et la plupart du temps irrécupérables en raison de l'immuabilité.
Bien que les chiffres varient, on estime que le montant total de la valeur volée ou perdue en raison de défauts de sécurité dans les contrats intelligents est d'au moins 1 milliard de dollars. Cela inclut des incidents très médiatisés, tels que le piratage de la DAOopens in a new tab (3,6M d'ETH volés, d'une valeur de plus de 1 milliard de dollars aux prix d'aujourd'hui), le piratage du portefeuille multi-signatures Parityopens in a new tab (30M de dollars perdus au profit des pirates), et le problème du portefeuille gelé de Parityopens in a new tab (plus de 300M de dollars en ETH bloqués à jamais).
Les problèmes susmentionnés rendent impératif pour les développeurs d'investir des efforts dans la construction de contrats intelligents sécurisés, robustes et résistants. La sécurité des contrats intelligents est une affaire sérieuse, que chaque développeur ferait bien d’apprendre. Ce guide couvrira les considérations de sécurité des développeurs Ethereum et explorera les ressources pour améliorer la sécurité des contrats intelligents.
Prérequis
Assurez-vous de bien connaître les principes fondamentaux du développement de contrats intelligents avant de vous attaquer à la sécurité.
Lignes directrices pour la création de contrats intelligents Ethereum sécurisés
1. Concevoir des contrôles d'accès appropriés
Dans les contrats intelligents, les fonctions marquées public ou external peuvent être appelées par n'importe quel compte externe (EOA) ou compte de contrat. Il est nécessaire de spécifier une visibilité publique des fonctions si vous voulez que les autres interagissent avec votre contrat. En revanche, les fonctions marquées private ne peuvent être appelées que par des fonctions au sein du contrat intelligent, et non par des comptes externes. Donner à chaque participant au réseau un accès aux fonctions du contrat peut causer des problèmes, surtout si cela signifie que n'importe qui peut effectuer des opérations sensibles (par exemple, frapper de nouveaux jetons).
Pour éviter l'utilisation non autorisée de fonctions de contrats intelligents, il est nécessaire de mettre en place des contrôles d'accès sécurisés. Les mécanismes de contrôle d'accès restreignent la capacité d'utiliser certaines fonctions dans un contrat intelligent à des entités approuvées, comme les comptes responsables de la gestion du contrat. Le modèle « Ownable » et le contrôle basé sur les rôles sont deux modèles utiles pour mettre en œuvre le contrôle d'accès dans les contrats intelligents :
Modèle « Ownable »
Dans le modèle Ownable, une adresse est définie comme « propriétaire » du contrat au cours du processus de création du contrat. Les fonctions protégées se voient attribuer un modificateur OnlyOwner, qui garantit que le contrat authentifie l'identité de l'adresse appelante avant d'exécuter la fonction. Les appels à des fonctions protégées à partir d'autres adresses en dehors du propriétaire du contrat s'annulent toujours, empêchant l'accès non désiré.
Contrôle d'accès basé sur les rôles
L'enregistrement d'une seule adresse en tant que Owner dans un contrat intelligent introduit le risque de centralisation et représente un point de défaillance unique. Si les clés de compte du propriétaire sont compromises, des attaquants peuvent attaquer le contrat détenu. C'est pourquoi utiliser un modèle de contrôle d'accès basé sur des rôles avec plusieurs comptes administratifs peut être une meilleure solution.
Dans le cadre du contrôle d'accès basé sur les rôles, l'accès aux fonctions sensibles est réparti entre un ensemble de participants de confiance. Par exemple, un compte peut être responsable de la frappe des jetons, tandis qu'un autre compte peut effectuer des mises à niveau ou interrompre le contrat. Décentraliser le contrôle d'accès de cette façon élimine les points de défaillance uniques et réduit les hypothèses de confiance pour les utilisateurs.
Utilisation de portefeuilles multi-signature
Une autre approche pour la mise en place d'un contrôle d'accès sécurisé consiste à utiliser un compte multi-signatures pour gérer un contrat. Contrairement à un EOA habituel, les comptes multi-signature sont détenus par plusieurs entités et nécessitent les signatures d'un nombre minimum de comptes — disons de 3 sur 5 — pour exécuter des transactions.
L'utilisation d'un portefeuille multi-signature pour le contrôle d'accès introduit une couche de sécurité supplémentaire dans la mesure où les actions sur le contrat cible nécessitent le consentement de plusieurs parties. Ceci est particulièrement utile si l'utilisation du modèle Ownable est nécessaire, car il rend plus difficile pour un attaquant ou un initié malhonnête de manipuler des fonctions sensibles du contrat à des fins malveillantes.
2. Utiliser les instructions require(), assert() et revert() pour protéger les opérations de contrat
Comme mentionné, n'importe qui peut appeler des fonctions publiques de votre contrat intelligent une fois qu'il est déployé sur la blockchain. Comme vous ne pouvez pas savoir à l'avance comment les comptes externes interagiront avec un contrat, il est idéal de mettre en œuvre des protections internes contre les opérations problématiques avant le déploiement. Vous pouvez imposer un comportement correct dans les contrats intelligents en utilisant les instructions require(), assert() et revert() pour déclencher des exceptions et annuler les changements d'état si l'exécution ne satisfait pas à certaines exigences.
require() : require est défini au début des fonctions et garantit que les conditions prédéfinies sont remplies avant l'exécution de la fonction appelée. Une instruction require peut être utilisée pour valider les entrées de l'utilisateur, vérifier les variables d'état ou authentifier l'identité du compte appelant avant de poursuivre avec une fonction.
assert() : assert() est utilisé pour détecter les erreurs internes et vérifier les violations des « invariants » dans votre code. Un invariant est une assertion logique à propos de l’état d’un contrat qui devrait être vrai pour toutes les exécutions de fonctions. Un exemple d'invariant est la quantité maximale totale ou le solde d'un contrat de jeton. L'utilisation de assert() garantit que votre contrat n'atteint jamais un état vulnérable, et si c'est le cas, toutes les modifications apportées aux variables d'état sont annulées.
revert() : revert() peut être utilisé dans une instruction if-else qui déclenche une exception si la condition requise n'est pas satisfaite. L'exemple de contrat ci-dessous utilise revert() pour protéger l'exécution des fonctions :
1pragma solidity ^0.8.4;23contract VendingMachine {4 address owner;5 error Unauthorized();6 function buy(uint amount) public payable {7 if (amount > msg.value / 2 ether)8 revert("Pas assez d'Ether fournis.");9 // Effectuer l'achat.10 }11 function withdraw() public {12 if (msg.sender != owner)13 revert Unauthorized();1415 payable(msg.sender).transfer(address(this).balance);16 }17}Afficher tout3. Tester les contrats intelligents et vérifier l'exactitude du code
L'immuabilité du code s'exécutant dans la machine virtulle Ethereum signifie que les contrats intelligents exigent un niveau d'évaluation de la qualité plus élevé pendant la phase de développement. Tester votre contrat de manière intensive et l'observer pour déceler tout résultat inattendu améliorera considérablement la sécurité et protégera vos utilisateurs sur le long terme.
La méthode habituelle est d'écrire de petits tests unitaires à l'aide de données fictives que le contrat devrait recevoir de la part des utilisateurs. Le test unitaire est utile pour tester la fonctionnalité de certaines fonctions et s'assurer qu'un contrat intelligent fonctionne comme prévu.
Malheureusement, les tests unitaires sont peu efficaces pour améliorer la sécurité des contrats intelligents lorsqu'ils sont utilisés isolément. Un test unitaire peut prouver qu'une fonction s'exécute correctement pour les données simulées, mais les tests unitaires sont seulement aussi efficaces que les tests écrits. Il est donc difficile de détecter les cas et les vulnérabilités marginaux manqués qui pourraient nuire à la sécurité de votre contrat intelligent.
Une meilleure approche consiste à combiner les tests unitaires avec des tests basés sur les propriétés effectués à l'aide de l'analyse statique et dynamique. L'analyse statique s'appuie sur des représentations de bas niveau, telles que des graphes de flux de contrôleopens in a new tab et des arbres de syntaxe abstraiteopens in a new tab pour analyser les états de programme et les chemins d'exécution accessibles. Pendant ce temps, les techniques d'analyse dynamique, telles que le fuzzing de contrats intelligentsopens in a new tab, exécutent le code du contrat avec des valeurs d'entrée aléatoires pour détecter les opérations qui violent les propriétés de sécurité.
La vérification formelle est une autre technique pour vérifier les propriétés de sécurité dans les contrats intelligents. Contrairement aux tests réguliers, la vérification formelle peut prouver de façon concluante l'absence d'erreurs dans un contrat intelligent. Ceci est réalisé en créant une spécification formelle qui permet de saisir les propriétés de sécurité désirées et de prouver qu'un modèle formel des contrats adhère à cette spécification.
4. Demander une révision indépendante de votre code
Après avoir testé votre contrat, il est bon de demander à d'autres de vérifier le code source pour tout problème de sécurité. Les tests ne décèleront pas toutes les failles d'un contrat intelligent, mais obtenir un examen indépendant augmente la possibilité de détecter les vulnérabilités.
Audits
Demander un audit des contrats intelligents est une façon de procéder à un examen indépendant du code. Les vérificateurs jouent un rôle important en veillant à ce que les contrats intelligents soient sécurisés et exempts de défauts de qualité et d'erreurs de conception.
Cela dit, évitez de considérer les audits comme un remède miracle. Les audits de contrats intelligents ne saisiront pas chaque bogue et sont principalement conçus pour fournir une série de revues complémentaires, qui peut aider à détecter les problèmes qui auront échappé aux développeurs lors du développement et du test initial. Vous devez également suivre les bonnes pratiques pour travailler avec les auditeurs, comme documenter le code correctement et ajouter des commentaires en ligne, pour maximiser les avantages d'un audit de contrats intelligents.
- Conseils et astuces pour l'audit de contrats intelligentsopens in a new tab - @tinchoabbate
- Tirez le meilleur parti de votre auditopens in a new tab - Inference
Primes de bogues
La mise en place d'un programme de prime de bogues est une autre approche pour implémenter des examens de code externes. Une prime de bogue est une récompense financière donnée aux individus (généralement des hackers whitehat) qui découvrent des vulnérabilités dans une application.
Lorsqu'elle est utilisée correctement, la primes de bogues incitent les membres de la communauté hacker à inspecter votre code pour trouver des défauts critiques. Un exemple concret est le « bogue de l'argent infini » qui aurait permis à un attaquant de créer une quantité illimitée d'ether sur Optimismopens in a new tab, un protocole de couche 2 fonctionnant sur Ethereum. Heureusement, un hacker éthique a découvert la failleopens in a new tab et a prévenu l'équipe, empochant au passage une récompense importanteopens in a new tab.
Une stratégie utile est de définir le paiement d'un programme de prime de bogues proportionnellement au montant des fonds mis en jeu. Décrite comme la « prime de bogue à l'échelleopens in a new tab », cette approche offre des incitations financières aux individus pour qu'ils divulguent les vulnérabilités de manière responsable au lieu de les exploiter.
5. Suivre les meilleures pratiques lors du développement de contrats intelligents
L’existence d’audits et de primes de bogue n'exclut pas votre responsabilité d’écrire un code de haute qualité. Une bonne sécurité du contrat intelligent commence en suivant des processus de conception et de développement adéquats :
-
Stocker tout le code dans un système de contrôle de version, tel que git
-
Effectuer toutes les modifications de code via des pulls requests
-
Assurez-vous que les pulls requests ont au moins un réviseur indépendant — si vous travaillez en solo sur un projet, envisagez de trouver d'autres développeurs et d'échanger mutuellement vos avis sur le code
-
Utiliser un environnement de développement pour tester, compiler et déployer des contrats intelligents
-
Exécutez votre code via des outils d'analyse de code de base, tels que Cyfrin Aderynopens in a new tab, Mythril et Slither. Idéalement, vous devriez le faire avant de fusionner chaque pull request et comparer les différences de sortie
-
Assurez-vous que votre code est compilé sans erreurs, et que le compilateur Solidity n'émet aucun avertissement
-
Documentez correctement votre code (en utilisant NatSpecopens in a new tab) et décrivez les détails de l'architecture du contrat dans un langage facile à comprendre. Cela facilitera l'audit et l'examen de votre code pour les autres.
6. Mettre en œuvre des plans de reprise après sinistre robustes
La conception de contrôles d'accès sécurisés, la mise en œuvre de modificateurs de fonction et d'autres suggestions peuvent améliorer la sécurité des contrats intelligents, mais elles ne peuvent pas exclure la possibilité d'exploits malveillants. Pour élaborer des contrats intelligents sécurisés, il faut se « préparer à l'échec » et disposer d'un plan de repli pour répondre efficacement aux attaques. Un plan de reprise après sinistre adéquat intègre tout ou partie des éléments suivants :
Mises à niveau de contrats
Bien que les contrats intelligents Ethereum soient immuables par défaut, il est possible d'obtenir un certain degré de mutabilité en utilisant des modèles de mise à niveau. La mise à niveau des contrats est nécessaire dans les cas où une faille critique rend votre ancien contrat inutilisable et où le déploiement d'une nouvelle logique est l'option la plus réalisable.
Les mécanismes de mise à niveau des contrats fonctionnent différemment, mais le « modèle proxy » est l'une des approches les plus populaires pour la mise à niveau des contrats intelligents. Les modèles de proxyopens in a new tab répartissent l'état et la logique d'une application entre deux contrats. Le premier contrat (appelé « contrat mandataire ») stocke les variables d'état (par exemple, les soldes des utilisateurs), tandis que le second contrat (appelé « contrat logique ») contient le code d'exécution des fonctions du contrat.
Les comptes interagissent avec le contrat proxy, qui délègue tous les appels de fonction au contrat logique en utilisant l'appel de bas niveau delegatecall()opens in a new tab. Contrairement à un appel de message classique, delegatecall() garantit que le code exécuté à l'adresse du contrat logique est exécuté dans le contexte du contrat appelant. Cela signifie que le contrat logique écrira toujours dans le stockage du proxy (au lieu de son propre stockage) et que les valeurs originales de msg.sender et msg.value sont préservées.
La délégation des appels au contrat logique nécessite de stocker son adresse dans le stockage du contrat de procuration. Par conséquent, la mise à niveau de la logique du contrat consiste simplement à déployer un autre contrat logique et à stocker la nouvelle adresse dans le contrat de procuration. Comme les appels ultérieurs au contrat de procuration sont automatiquement acheminés vers le nouveau contrat logique, vous aurez « mis à niveau » le contrat sans modifier réellement le code.
En savoir plus sur la mise à niveau des contrats.
Arrêts d'urgence
Comme nous l'avons mentionné, les audits et les tests approfondis ne peuvent pas découvrir tous les bugs d'un contrat intelligent. Si une vulnérabilité apparaît dans votre code après le déploiement, il est impossible de la corriger puisque vous ne pouvez pas modifier le code exécuté à l'adresse du contrat. De plus, les mécanismes de mise à niveau ( par exemple, les modèles de procuration) peuvent prendre du temps à se mettre en œuvre (ils nécessitent souvent l'approbation de différentes parties), ce qui ne fait que donner plus de temps aux attaquants pour causer plus de dommages.
L'option nucléaire consiste à mettre en œuvre une fonction « d'arrêt d'urgence » qui bloque les appels aux fonctions vulnérables dans un contrat. Les arrêts d'urgence comprennent généralement les composants suivants :
-
Une variable booléenne globale indiquant si le contrat intelligent est dans un état arrêté ou non. Cette variable est initialisée à
falselors de la configuration du contrat, mais reviendra àtrueune fois le contrat arrêté. -
Les fonctions qui font référence à la variable booléenne dans leur exécution. Ces fonctions sont accessibles lorsque le contrat intelligent n'est pas arrêté, et deviennent inaccessibles lorsque la fonction d'arrêt d'urgence est déclenchée.
-
Une entité qui a accès à la fonction d'arrêt d'urgence, qui définit la variable booléenne à
true. Pour éviter les actions malveillantes, les appels à cette fonction peuvent être limités à une adresse de confiance (par exemple, le propriétaire du contrat).
Une fois que le contrat a activé l'arrêt d'urgence, certaines fonctions ne seront pas appelables. Pour ce faire, les fonctions de sélection sont enveloppées dans un modificateur qui fait référence à la variable globale. Vous trouverez ci-dessous un exempleopens in a new tab décrivant une mise en œuvre de ce modèle dans les contrats :
1// Ce code n'a pas été audité par des professionnels et ne fait aucune promesse quant à sa sécurité ou son exactitude. Utilisez-le à vos propres risques.23contract EmergencyStop {45 bool isStopped = false;67 modifier stoppedInEmergency {8 require(!isStopped);9 _;10 }1112 modifier onlyWhenStopped {13 require(isStopped);14 _;15 }1617 modifier onlyAuthorized {18 // Vérifier l'autorisation de msg.sender ici19 _;20 }2122 function stopContract() public onlyAuthorized {23 isStopped = true;24 }2526 function resumeContract() public onlyAuthorized {27 isStopped = false;28 }2930 function deposit() public payable stoppedInEmergency {31 // La logique de dépôt s'exécute ici32 }3334 function emergencyWithdraw() public onlyWhenStopped {35 // Le retrait d'urgence s'exécute ici36 }37}Afficher toutCet exemple montre les caractéristiques de base des arrêts d'urgence :
-
isStoppedest un booléen qui s'évalue àfalseau début et àtruelorsque le contrat entre en mode d'urgence. -
Les modificateurs de fonction
onlyWhenStoppedetstoppedInEmergencyvérifient la variableisStopped.stoppedInEmergencyest utilisé pour contrôler les fonctions qui devraient être inaccessibles lorsque le contrat est vulnérable (par ex.,deposit()). Les appels à ces fonctions seront tout simplement annulés.
onlyWhenStopped est utilisé pour les fonctions qui doivent pouvoir être appelées en cas d'urgence (par ex., emergencyWithdraw()). De telles fonctions peuvent aider à résoudre la situation, d’où leur exclusion de la liste des « fonctions restreintes ».
L'utilisation d'une fonctionnalité d'arrêt d'urgence constitue un palliatif efficace pour faire face aux vulnérabilités graves de votre contrat intelligent. Cependant, les utilisateurs doivent faire confiance aux développeurs pour qu'ils ne l'activent pas pour des raisons intéressées. À cette fin, il est possible de décentraliser le contrôle de l'arrêt d'urgence en le soumettant à un mécanisme de vote sur la chaîne, à un timelock, ou à l'approbation d'un portefeuille multisig.
Surveillance des événements
Les événementsopens in a new tab vous permettent de suivre les appels aux fonctions des contrats intelligents et de surveiller les changements des variables d'état. Il est idéal de programmer votre contrat intelligent pour qu'il émette un événement chaque fois qu'une partie prend une mesure critique en matière de sécurité (par exemple, retirer des fonds).
L'enregistrement des événements et leur surveillance hors chaîne permettent de mieux comprendre les opérations contractuelles et de découvrir plus rapidement les actions malveillantes. Cela signifie que votre équipe peut réagir plus rapidement aux hacks et prendre des mesures pour atténuer l'impact sur les utilisateurs, tels que suspendre les fonctions ou effectuer une mise à niveau.
Vous pouvez également opter pour un outil de surveillance en vente libre qui transmet automatiquement les alertes lorsque quelqu'un interagit avec vos contrats. Ces outils vous permettent de créer des alertes personnalisées basées sur différents déclencheurs, comme le volume de la transaction, la fréquence des appels de fonctions, ou les fonctions spécifiques impliquées. Par exemple, vous pouvez programmer une alerte qui arrive lorsque le montant retiré en une seule opération dépasse un seuil particulier.
7. Concevoir des systèmes de gouvernance sécurisés
Vous voudrez peut-être décentraliser votre application en transférant le contrôle des contrats intelligents de base aux membres de la communauté. Dans ce cas, le système de contrats intelligents comprendra un module de gouvernance, à savoir un mécanisme qui permet aux membres de la communauté d'approuver des actions administratives via un système de gouvernance en chaîne. Par exemple, une proposition de mise à niveau d'un contrat de procuration vers une nouvelle implémentation peut être votée par les détenteurs de jetons.
Une gouvernance décentralisée peut être bénéfique, en particulier parce qu'elle aligne les intérêts des développeurs et des utilisateurs finaux. Néanmoins, les mécanismes de gouvernance des contrats intelligents peuvent introduire de nouveaux risques s'ils sont mal mis en œuvre. Un scénario plausible serait celui où un attaquant acquiert un pouvoir de vote énorme (mesuré en nombre de jetons détenus) en contractant un prêt flash et en faisant passer une proposition malveillante.
Une façon de prévenir les problèmes liés à la gouvernance en chaîne est d'utiliser un verrouillage temporelopens in a new tab. Un timelock empêche un contrat intelligent d'exécuter certaines actions jusqu'à ce qu'un certain temps passe. D'autres stratégies incluent l'assignation d'une « pondération de vote » à chaque jeton en fonction de la durée d'enfermement de chaque jeton, ou mesurant le pouvoir de vote d'une adresse à une période historique (par exemple, 2-3 blocs dans le passé) au lieu du bloc actuel. Les deux méthodes réduisent la possibilité de récupérer rapidement le pouvoir de vote pour faire basculer des votes sur la chaîne.
Pour en savoir plus sur la conception de systèmes de gouvernance sécurisésopens in a new tab, les différents mécanismes de vote dans les DAOopens in a new tab, et les vecteurs d'attaque courants des DAO qui tirent parti de la DeFiopens in a new tab, consultez les liens partagés.
8. Réduire la complexité du code au minimum
Les développeurs de logiciels traditionnels sont familiers avec le principe KISS (« keep it simple, stupid ») qui recommande de ne pas introduire de complexité inutile dans la conception de logiciels. Cela fait suite à la pensée de longue date selon laquelle « les systèmes complexes échouent de manière complexe » et sont plus susceptibles d’être confrontés à des erreurs coûteuses.
Garder les choses simples est particulièrement important lors de la rédaction de contrats intelligents, étant donné que les contrats intelligents contrôlent potentiellement de grandes quantités de valeur. Une astuce pour atteindre la simplicité lors de l'écriture de contrats intelligents est de réutiliser les bibliothèques existantes, telles que les Contrats OpenZeppelinopens in a new tab, lorsque cela est possible. Parce que ces bibliothèques ont été largement vérifiées et testées par les développeurs, leur utilisation réduit les chances d'introduire des bogues en écrivant de nouvelles fonctionnalités à partir de zéro.
Un autre conseil commun est d'écrire de petites fonctions et de garder les contrats modulaires en divisant la logique commerciale entre plusieurs contrats. Non seulement l'écriture de code plus simple réduit la surface d'attaque dans un contrat intelligent, mais il est également plus facile de raisonner sur la justesse du système global et de détecter les éventuelles erreurs de conception plus tôt.
9. Se défendre contre les vulnérabilités courantes des contrats intelligents
Réentrance
L’EVM ne permet pas la simultanéité, ce qui signifie que deux contrats impliqués dans un appel de message ne peuvent pas être exécutés simultanément. Un appel externe met en pause l'exécution et la mémoire du contrat d'appel jusqu'à ce que l'appel revienne, à partir duquel l'exécution du point se déroule normalement. Ce processus peut être formellement décrit comme le transfert du flux de contrôleopens in a new tab vers un autre contrat.
Bien que la plupart du temps inoffensifs, le transfert de flux de contrôle vers des contrats non approuvés peut causer des problèmes, tels que la réentrance. Une attaque par réentrance survient lorsqu'un contrat malveillant rappelle un contrat vulnérable avant que l'invocation de la fonction d'origine ne soit terminée. Ce type d'attaque est mieux expliqué avec un exemple.
Considérez un simple contrat intelligent (« Victim ») qui permet à quiconque de déposer et de retirer de l'Ether :
1// Ce contrat est vulnérable. Ne pas utiliser en production23contract Victim {4 mapping (address => uint256) public balances;56 function deposit() external payable {7 balances[msg.sender] += msg.value;8 }910 function withdraw() external {11 uint256 amount = balances[msg.sender];12 (bool success, ) = msg.sender.call.value(amount)("");13 require(success);14 balances[msg.sender] = 0;15 }16}Afficher toutCe contrat expose une fonction withdraw() pour permettre aux utilisateurs de retirer des ETH précédemment déposés dans le contrat. Lors du traitement d'un retrait, le contrat effectue les opérations suivantes :
- Vérifie le solde ETH de l'utilisateur
- Envoie des fonds à l'adresse d'appel
- Réinitialise son solde à 0, empêchant les retraits supplémentaires de l'utilisateur
La fonction withdraw() dans le contrat Victim suit un modèle « vérifications-interactions-effets ». Il vérifie si les conditions nécessaires à l'exécution sont satisfaites (c.-à-d. que l'utilisateur a un solde d'ETH positif) et effectue l'interaction en envoyant des ETH à l'adresse de l'appelant, avant d'appliquer les effets de la transaction (c.-à-d. en réduisant le solde de l'utilisateur).
Si withdraw() est appelé depuis un compte externe (EOA), la fonction s'exécute comme prévu : msg.sender.call.value() envoie des ETH à l'appelant. Cependant, si msg.sender est un compte de contrat intelligent qui appelle withdraw(), l'envoi de fonds via msg.sender.call.value() déclenchera également l'exécution du code stocké à cette adresse.
Imaginez qu'il s'agisse du code déployé à l'adresse du contrat:
1 contract Attacker {2 function beginAttack() external payable {3 Victim(victim_address).deposit.value(1 ether)();4 Victim(victim_address).withdraw();5 }67 function() external payable {8 if (gasleft() > 40000) {9 Victim(victim_address).withdraw();10 }11 }12}Afficher toutCe contrat est conçu pour faire trois choses :
- Accepter un dépôt depuis un autre compte (probablement l’EOA de l’attaquant)
- Dépose 1 ETH dans le contrat Victim
- Retirer 1 ETH stocké dans le contrat intelligent
Il n'y a rien de mal ici, si ce n'est que Attacker a une autre fonction qui appelle à nouveau withdraw() dans Victim si le gaz restant de l'appel entrant msg.sender.call.value est supérieur à 40 000. Cela donne à Attacker la possibilité de ré-entrer dans Victim et de retirer plus de fonds avant la fin de la première invocation de withdraw. Le cycle ressemble à ceci:
1- L'EOA de l'attaquant appelle `Attacker.beginAttack()` avec 1 ETH2- `Attaquant.beginAttack()` dépose 1 ETH dans `Victim`3- `Attacker` appelle `withdraw() dans `Victim`4- `Victim` vérifie le solde de `Attacker` (1 ETH)5- `Victim` envoie 1 ETH à `Attacker` (qui déclenche la fonction par défaut)6- `Attacker` appelle `Victim.withdraw()` à nouveau (notez que `Victim` n'a pas réduit le solde de `Attacker` à partir du premier retrait)7- `Victim` vérifie le solde de `Attacker` (qui est toujours 1 ETH car il n'a pas appliqué les effets du premier appel)8- `Victim` envoie 1 ETH à `Attacker` (qui déclenche la fonction par défaut et permet à `Attacker` de réintroduire la fonction `withdraw`)9- Le processus se répète jusqu'à ce que `Attacker` soit épuisé, à quel point `msg.sender.call.value` retourne sans déclencher de retraits supplémentaires10- `Victim` applique enfin les résultats de la première transaction (et de celles subséquentes) à son état, donc le solde de `Attacker` est fixé à 0Afficher toutLe résumé est que, comme le solde de l'appelant n'est pas défini à 0 jusqu'à ce que l'exécution de la fonction soit terminée, les invocations suivantes réussiront et permettront à l'appelant de retirer son solde plusieurs fois. Ce type d'attaque peut être utilisé pour vider les fonds d'un contrat intelligent, comme ce qui s'est passé lors du piratage de la DAO en 2016opens in a new tab. Les attaques par réentrance restent aujourd'hui un problème critique pour les contrats intelligents, comme le montrent les listes publiques d'exploits de réentranceopens in a new tab.
Comment empêcher les attaques par réentrance
Une approche pour gérer la réentrance consiste à suivre le modèle vérifications-effets-interactionsopens in a new tab. Ce modèle ordonne l'exécution de fonctions d'une manière que le code qui effectue les vérifications nécessaires avant de progresser avec l'exécution arrive en premier, suivi du code qui manipule l'état du contrat, avec du code qui interagit avec d'autres contrats ou EOA arrivant en dernier.
Le modèle vérifications-effets-interactions est utilisé dans une version révisée du contrat Victim présentée ci-dessous :
1contract NoLongerAVictim {2 function withdraw() external {3 uint256 amount = balances[msg.sender];4 balances[msg.sender] = 0;5 (bool success, ) = msg.sender.call.value(amount)("");6 require(success);7 }8}Ce contrat effectue une vérification du solde de l'utilisateur, applique les effets de la fonction withdraw() (en réinitialisant le solde de l'utilisateur à 0), et procède à l'interaction (envoi d'ETH à l'adresse de l'utilisateur). Cela garantit que le contrat met à jour son stockage avant l’appel externe, éliminant ainsi la condition de réentrance qui a permis la première attaque. Le contrat Attacker pourrait toujours rappeler NoLongerAVictim, mais comme balances[msg.sender] a été mis à 0, les retraits supplémentaires lèveront une erreur.
Une autre option est d'utiliser un verrou d'exclusion mutuelle (communément décrit comme un « mutex ») qui verrouille une partie de l'état d'un contrat jusqu'à ce qu'une invocation de fonction soit terminée. Ceci est mis en œuvre à l'aide d'une variable booléenne qui est définie sur true avant l'exécution de la fonction et revient à false une fois l'invocation terminée. Comme on le voit dans l'exemple ci-dessous, l'utilisation d'un mutex protège une fonction contre les appels récursifs alors que l'invocation originale est toujours en cours de traitement, empêchant ainsi efficacement la réentrance.
1pragma solidity ^0.7.0;23contract MutexPattern {4 bool locked = false;5 mapping(address => uint256) public balances;67 modifier noReentrancy() {8 require(!locked, "Bloqué par la réentrance.");9 locked = true;10 _;11 locked = false;12 }13 // Cette fonction est protégée par un mutex, donc les appels réentrants depuis `msg.sender.call` ne peuvent plus appeler `withdraw`.14 // L'instruction `return` s'évalue à `true` mais évalue quand même l'instruction `locked = false` dans le modificateur15 function withdraw(uint _amount) public payable noReentrancy returns(bool) {16 require(balances[msg.sender] >= _amount, "Pas de solde à retirer.");1718 balances[msg.sender] -= _amount;19 (bool success, ) = msg.sender.call{value: _amount}("");20 require(success);2122 return true;23 }24}Afficher toutVous pouvez également utiliser un système de paiements pullopens in a new tab qui exige que les utilisateurs retirent des fonds des contrats intelligents, au lieu d'un système de « paiements push » qui envoie des fonds aux comptes. Cela élimine la possibilité de déclencher par inadvertance du code à des adresses inconnues (et peut également prévenir certaines attaques par déni de service).
Dépassements négatifs et positifs d'entiers
Un dépassement d'entier se produit lorsque les résultats d'une opération arithmétique tombent en dehors de la plage de valeurs acceptable, le faisant passer à la valeur représentable la plus basse. Par exemple, un uint8 ne peut stocker que des valeurs allant jusqu'à 2^8-1=255. Les opérations arithmétiques qui aboutissent à des valeurs supérieures à 255 provoqueront un dépassement positif et réinitialiseront uint à 0, de la même manière que l'odomètre d'une voiture se remet à 0 lorsqu'il atteint son kilométrage maximum (999999).
Les dépassements négatifs d'entiers se produisent pour des raisons similaires : les résultats d'une opération arithmétique tombent en dessous de la plage acceptable. Si vous essayiez de décrémenter 0 dans un uint8, le résultat reviendrait simplement à la valeur maximale représentable (255).
Les dépassements d'entier et les soupassements peuvent entraîner des changements inattendus dans les variables d'état d'un contrat et entraîner une exécution non planifiée. Voici un exemple montrant comment un attaquant peut exploiter un dépassement arithmétique dans un contrat intelligent pour effectuer une opération invalide :
1pragma solidity ^0.7.6;23// Ce contrat est conçu pour agir comme un coffre-fort temporel.4// L'utilisateur peut déposer dans ce contrat, mais ne peut pas retirer pendant au moins une semaine.5// L'utilisateur peut également prolonger le temps d'attente au-delà de la période d'attente d'une semaine.67/*81. Déployer TimeLock92. Déployer Attack avec l'adresse de TimeLock103. Appeler Attack.attack en envoyant 1 ether. Vous pourrez immédiatement retirer11 votre ether.1213Que s'est-il passé ?14Attack a provoqué le dépassement de TimeLock.lockTime et a permis un retrait15avant la période d'attente d'une semaine.16*/1718contract TimeLock {19 mapping(address => uint) public balances;20 mapping(address => uint) public lockTime;2122 function deposit() external payable {23 balances[msg.sender] += msg.value;24 lockTime[msg.sender] = block.timestamp + 1 weeks;25 }2627 function increaseLockTime(uint _secondsToIncrease) public {28 lockTime[msg.sender] += _secondsToIncrease;29 }3031 function withdraw() public {32 require(balances[msg.sender] > 0, "Fonds insuffisants");33 require(block.timestamp > lockTime[msg.sender], "Le temps de verrouillage n'est pas expiré");3435 uint amount = balances[msg.sender];36 balances[msg.sender] = 0;3738 (bool sent, ) = msg.sender.call{value: amount}("");39 require(sent, "Échec de l'envoi d'Ether");40 }41}4243contract Attack {44 TimeLock timeLock;4546 constructor(TimeLock _timeLock) {47 timeLock = TimeLock(_timeLock);48 }4950 fallback() external payable {}5152 function attack() public payable {53 timeLock.deposit{value: msg.value}();54 /*55 si t = temps de verrouillage actuel, nous devons trouver x tel que56 x + t = 2**256 = 057 donc x = -t58 2**256 = type(uint).max + 159 donc x = type(uint).max + 1 - t60 */61 timeLock.increaseLockTime(62 type(uint).max + 1 - timeLock.lockTime(address(this))63 );64 timeLock.withdraw();65 }66}Afficher toutComment éviter les soupassements et dépassements d'entier
Depuis la version 0.8.0, le compilateur Solidity rejette le code qui entraîne des soupassements et dépassements d'entier. Cependant, les contrats compilés avec une version plus ancienne du compilateur doivent soit effectuer des vérifications sur les fonctions impliquant des opérations arithmétiques, soit utiliser une bibliothèque (par ex., SafeMathopens in a new tab) qui vérifie les dépassements négatifs/positifs.
Manipulation d'oracle
Les oracles collectent des informations hors chaîne et les envoient sur la chaîne pour que les contrats intelligents puissent les utiliser. Avec des oracles, vous pouvez concevoir des contrats intelligents qui interagissent avec des systèmes hors chaîne, tels que les marchés de capitaux, élargissant ainsi considérablement leur application.
Mais si l'oracle est corrompu et envoie des informations incorrectes sur la chaîne, les contrats intelligents s'exécuteront sur la base d'entrées erronées, ce qui peut causer des problèmes. C'est la base du « problème de l'oracle », qui concerne la tâche de s'assurer que les informations provenant d'un oracle de la blockchain sont exactes, à jour et en temps opportun.
L'utilisation d'un oracle sur la chaîne, tel qu'un échange décentralisé, pour obtenir le prix comptant d'un actif, pose un problème de sécurité connexe. Les plateformes de prêt dans le secteur de la finance décentralisée (DeFi) le font souvent pour déterminer la valeur de la garantie d'un utilisateur afin de déterminer combien il peut emprunter.
Les prix des DEX sont souvent exacts, en grande partie en raison du rétablissement de la parité sur les marchés. Cependant, ils sont ouverts à la manipulation, en particulier si l'oracle sur la chaîne calcule les prix des actifs en fonction des modèles de négociation historiques (comme c'est généralement le cas).
Par exemple, un attaquant pourrait artificiellement pomper le prix au comptant d'un actif en souscrivant un prêt flash juste avant d'interagir avec votre contrat de prêt. Interroger le DEX pour le prix de l’actif reviendrait à une valeur plus élevée que la normale (en raison de la forte demande de transfert de « l'ordre d’achat » de l’attaquant pour l’actif), leur permettant d'emprunter plus qu'ils ne le devraient. De telles « attaques de prêts flash » ont été utilisées pour exploiter la dépendance à l'égard de prix oracles parmi les applications DeFi, coûtant des millions de protocoles en fonds perdus.
Comment éviter la manipulation d'oracle
L'exigence minimale pour éviter la manipulation d'oracleopens in a new tab est d'utiliser un réseau d'oracles décentralisé qui interroge des informations provenant de plusieurs sources pour éviter les points de défaillance uniques. Dans la plupart des cas, les oracles décentralisés ont des incitations cryptoéconomiques intégrées pour encourager les noeuds d'oracle à signaler des informations correctes, les rendant plus sûres que les oracles centralisés.
Si vous comptez interroger un oracle sur la chaîne sur le prix des actifs, pensez à utiliser un mécanisme qui implémente un prix moyen pondéré (« Time Weighted Average Price », ou TWAP). Un oracle TWAPopens in a new tab interroge le prix d'un actif à deux moments différents (que vous pouvez modifier) et calcule le prix au comptant sur la base de la moyenne obtenue. Le choix de périodes plus longues protège votre protocole contre la manipulation des prix car les larges ordres exécutés récemment ne peuvent pas affecter les prix des actifs.
Ressources sur la sécurité des contrats intelligents pour les développeurs
Outils pour analyser les contrats intelligents et vérifier l'exactitude du code
-
Outils et bibliothèques de test - Collection d'outils et de bibliothèques standards pour effectuer des tests unitaires, des analyses statiques et dynamiques sur les contrats intelligents.
-
Outils de vérification formelle - Outils pour vérifier l'exactitude fonctionnelle des contrats intelligents et contrôler les invariants.
-
Services d'audit de contrats intelligents - Liste d'organisations fournissant des services d'audit de contrats intelligents pour les projets de développement Ethereum.
-
Plateformes de primes de bogues - Plateformes pour coordonner les primes de bogues et récompenser la divulgation responsable de vulnérabilités critiques dans les contrats intelligents.
-
Fork Checkeropens in a new tab - Un outil en ligne gratuit pour vérifier toutes les informations disponibles concernant un contrat forké.
-
ABI Encoderopens in a new tab - Un service en ligne gratuit pour encoder les fonctions de votre contrat Solidity et les arguments du constructeur.
-
Aderynopens in a new tab - Analyseur statique de Solidity, qui parcourt les arbres de syntaxe abstraite (AST) pour identifier les vulnérabilités suspectes et afficher les problèmes dans un format Markdown facile à consulter.
Outils de surveillance des contrats intelligents
- Tenderly Real-Time Alertingopens in a new tab - Un outil pour recevoir des notifications en temps réel lorsque des événements inhabituels ou inattendus se produisent sur vos contrats intelligents ou vos portefeuilles.
Outils pour l'administration sécurisée des contrats intelligents
-
Safeopens in a new tab - Portefeuille de contrat intelligent fonctionnant sur Ethereum qui nécessite qu'un nombre minimum de personnes approuvent une transaction avant qu'elle ne puisse avoir lieu (M sur N).
-
Contrats OpenZeppelinopens in a new tab - Bibliothèques de contrats pour la mise en œuvre de fonctionnalités administratives, y compris la propriété des contrats, les mises à niveau, les contrôles d'accès, la gouvernance, la mise en pause, et plus encore.
Services d'audit de contrats intelligents
-
ConsenSys Diligenceopens in a new tab - Service d'audit de contrats intelligents aidant les projets de l'écosystème blockchain à s'assurer que leurs protocoles sont prêts à être lancés et conçus pour protéger les utilisateurs.
-
CertiKopens in a new tab - Entreprise de sécurité blockchain pionnière dans l'utilisation de la technologie de vérification formelle de pointe sur les contrats intelligents et les réseaux blockchain.
-
Trail of Bitsopens in a new tab - Entreprise de cybersécurité qui combine la recherche en sécurité avec une mentalité d'attaquant pour réduire les risques et renforcer le code.
-
PeckShieldopens in a new tab - Société de sécurité blockchain proposant des produits et services pour la sécurité, la confidentialité et la convivialité de l'ensemble de l'écosystème blockchain.
-
QuantStampopens in a new tab - Service d'audit facilitant l'adoption généralisée de la technologie blockchain par le biais de services de sécurité et d'évaluation des risques.
-
OpenZeppelinopens in a new tab - Société de sécurité de contrats intelligents fournissant des audits de sécurité pour les systèmes distribués.
-
Runtime Verificationopens in a new tab - Société de sécurité spécialisée dans la modélisation et la vérification formelles des contrats intelligents.
-
Hackenopens in a new tab - Auditeur en cybersécurité Web3 apportant une approche à 360 degrés à la sécurité de la blockchain.
-
Nethermindopens in a new tab - Services d'audit Solidity et Cairo, garantissant l'intégrité des contrats intelligents et la sécurité des utilisateurs sur Ethereum et Starknet.
-
HashExopens in a new tab - HashEx se concentre sur l'audit de la blockchain et des contrats intelligents pour garantir la sécurité des cryptomonnaies, en fournissant des services tels que le développement de contrats intelligents, les tests de pénétration, le conseil en blockchain.
-
Code4renaopens in a new tab - Plateforme d'audit compétitive qui incite les experts en sécurité des contrats intelligents à trouver des vulnérabilités et à contribuer à rendre le web3 plus sécurisé.
-
CodeHawksopens in a new tab - Plateforme d'audits compétitifs hébergeant des concours d'audit de contrats intelligents pour les chercheurs en sécurité.
-
Cyfrinopens in a new tab - Référence en matière de sécurité Web3, incubant la sécurité crypto par le biais de produits et de services d'audit de contrats intelligents.
-
ImmuneBytesopens in a new tab - Société de sécurité Web3 proposant des audits de sécurité pour les systèmes blockchain par le biais d'une équipe d'auditeurs expérimentés et d'outils de premier ordre.
-
Oxorioopens in a new tab - Audits de contrats intelligents et services de sécurité blockchain avec une expertise en EVM, Solidity, ZK, et technologie inter-chaînes pour les entreprises crypto et les projets DeFi.
-
Inferenceopens in a new tab - _Société d'audit de sécurité, spécialisée dans l'audit de contrats intelligents pour les blockchains basées sur l'EVM. Grâce à ses auditeurs experts, elle identifie les problèmes potentiels et suggère des solutions concrètes pour les corriger avant le déploiement.
Plateformes de primes de bogues
-
Immunefiopens in a new tab - Plateforme de primes de bogues pour les contrats intelligents et les projets DeFi, où les chercheurs en sécurité examinent le code, divulguent les vulnérabilités, sont payés et rendent la crypto plus sûre.
-
HackerOneopens in a new tab - Plateforme de coordination des vulnérabilités et de primes de bogues qui met en relation des entreprises avec des testeurs d'intrusion et des chercheurs en cybersécurité.
-
HackenProofopens in a new tab - Plateforme de primes de bogues experte pour les projets crypto (DeFi, Contrats Intelligents, Portefeuilles, CEX et plus), où des professionnels de la sécurité fournissent des services de triage et où les chercheurs sont payés pour des rapports de bogues pertinents et vérifiés.
-
Sherlockopens in a new tab - Souscripteur en Web3 pour la sécurité des contrats intelligents, avec des paiements pour les auditeurs gérés via des contrats intelligents pour garantir que les bogues pertinents sont payés équitablement.
-
CodeHawksopens in a new tab - Plateforme compétitive de primes aux bogues où les auditeurs participent à des concours et à des défis de sécurité, et (bientôt) à leurs propres audits privés.
Publications sur les vulnérabilités et exploits connus des contrats intelligents
-
ConsenSys : Attaques connues sur les contrats intelligentsopens in a new tab - Explication conviviale pour les débutants des vulnérabilités de contrat les plus importantes, avec des exemples de code pour la plupart des cas.
-
Registre SWCopens in a new tab - Liste organisée d'éléments de l'énumération des faiblesses communes (CWE) qui s'appliquent aux contrats intelligents Ethereum.
-
Rektopens in a new tab - Publication régulièrement mise à jour des piratages et exploits de crypto de premier plan, avec des rapports post-mortem détaillés.
Défis pour l'apprentissage de la sécurité des contrats intelligents
-
Awesome BlockSec CTFopens in a new tab - Liste organisée de wargames, de défis et de compétitions de Capture du drapeauopens in a new tab sur la sécurité de la blockchain, ainsi que des solutions.
-
Damn Vulnerable DeFiopens in a new tab - Wargame pour apprendre la sécurité offensive des contrats intelligents DeFi et développer des compétences en matière de chasse aux bogues et d'audit de sécurité.
-
Ethernautopens in a new tab - Wargame basé sur Web3/Solidity où chaque niveau est un contrat intelligent qui doit être « piraté ».
-
HackenProof x HackTheBoxopens in a new tab - Défi de piratage de contrats intelligents, situé dans une aventure fantastique. La résolution du défi donne également accès à un programme privé de primes de bugs.
Meilleures pratiques pour la sécurisation des contrats intelligents
-
ConsenSys : Meilleures pratiques en matière de sécurité des contrats intelligents Ethereumopens in a new tab - Liste complète de lignes directrices pour sécuriser les contrats intelligents Ethereum.
-
Nascent : Boîte à outils de sécurité simpleopens in a new tab - Collection de guides pratiques axés sur la sécurité et de listes de contrôle pour le développement de contrats intelligents.
-
Modèles Solidityopens in a new tab - Compilation utile de modèles sécurisés et de meilleures pratiques pour Solidity, le langage de programmation de contrats intelligents.
-
Documents Solidity : Considérations de sécuritéopens in a new tab - Lignes directrices pour l'écriture de contrats intelligents sécurisés avec Solidity.
-
Norme de vérification de la sécurité des contrats intelligentsopens in a new tab - Liste de contrôle en quatorze parties créée pour normaliser la sécurité des contrats intelligents pour les développeurs, les architectes, les auditeurs de sécurité et les fournisseurs.
-
Apprendre la sécurité et l'audit des contrats intelligentsopens in a new tab - Le cours ultime sur la sécurité et l'audit des contrats intelligents, créé pour les développeurs de contrats intelligents qui cherchent à améliorer leurs meilleures pratiques en matière de sécurité et à devenir des chercheurs en sécurité.
Tutoriels sur la sécurité des contrats intelligents
-
Comment utiliser Slither pour trouver des bogues dans les contrats intelligents
-
Comment utiliser Manticore pour trouver des bogues dans les contrats intelligents
-
Lignes directrices sur la sécurité des contrats intelligents
-
Comment intégrer en toute sécurité votre contrat de jeton avec des jetons arbitraires