Sécurité
Les contrats intelligents Ethereum sont extrêmement flexibles, capables à la fois de détenir de grandes quantités de jetons (souvent plus de 1 milliard de $) et d'exécuter une logique immuable sur du code intelligent précédemment déployé. Alors que cela a créé un écosystème dynamique et créatif de contrats intelligents interconnectés et trustless, c'est aussi l'écosystème parfait pour attirer les attaquants qui cherchent à en tirer profit en exploitant des vulnérabilités dans les contrats intelligents et des comportements inattendus dans Ethereum. Le code d'un contrat intelligent ne peut habituellement pas être modifié pour corriger les défauts de sécurité, les actifs volés à partir de contrats intelligents sont irrécupérables et extrêmement difficiles à tracer. Le montant total volé ou perdu en raison de problèmes sur les contrats intelligents atteint facilement le milliard de dollars. Voici quelques-uns des problèmes les plus importants dus à des erreurs de codage de contrat intelligent :
- Problème n°1 de multisig Parity - 30 millions de dollars perdus
- Problème n°2 de multisig Parity - 300 millions de dollars perdus
- Hack TheDAO, 3,6 millions d'ETH ! Prés de 1 milliard de dollars en prix actuel de l'ETH
Prérequis
Cette page couvre la sécurité des contrats intelligents, donc assurez-vous d'être familiarisé avec les contrats intelligents avant d'aborder la sécurité.
Comment écrire un code de contrats intelligents plus sécurisé
Avant de lancer un code sur le réseau principal, il est important de prendre des précautions suffisantes pour protéger tout ce qui a de la valeur et qui est confié à votre contrat intelligent. Dans cet article, nous allons discuter de quelques attaques spécifiques, fournir des ressources pour en savoir plus sur les types d'attaques, et vous donner quelques outils et bonnes pratiques de base pour assurer le bon fonctionnement et la sécurité de vos contrats.
Les audits ne sont pas une solution miracle
Il y a des années, les outils permettant d'écrire, de compiler, de tester et de déployer des contrats intelligents manquaient de maturité, ce qui a généré de nombreux projets désordonnés d'écriture de code Solidity, code qui était ensuite transmis à un expert pour examen afin de s'assurer qu'il fonctionnait de façon sécurisée et comme prévu. En 2020, les processus de développement et les outils qui prennent en charge l'écriture de Solidity sont nettement meilleurs. En exploiter les bonnes pratiques permet non seulement de garantir que votre projet est plus facile à gérer, mais cela constitue un élément essentiel de sa sécurité. Un audit en fin de rédaction de votre contrat intelligent ne suffit plus comme seule considération de sécurité. La sécurité commence bien avant d'écrire votre première ligne de code de contrat intelligent, elle commence par des processus de conception et de développement adéquats.
Processus de développement de contrats intelligents
Il faut au minimum que :
- tout le code soit stocké dans un système de contrôle de version comme git ;
- toutes les modifications de code soient effectuées via des PR (Pull Requests) ;
- toutes les PR aient au moins un réviseur ; Si vous travaillez sur un projet en solo, envisagez de trouver un autre auteur solo pour négocier des révisions de code !
- une seule commande compile, déploie et exécute une suite de tests sur votre code en utilisant un environnement de développement Ethereum (Voir : Truffle) ;
- vous ayez exécuté votre code via des outils d'analyse basiques comme Mythril et Slither, idéalement avant que chaque PR soit fusionnée, en comparant les différences de sortie ;
- Solidity n'émette aucune alerte à la compilation ;
- votre code soit bien documenté.
Il y a bien d'autres choses à dire sur les processus de développement, mais ces éléments constituent un bon point de départ. Pour plus d'éléments et d'explications détaillées, consultez la liste de contrôle qualité des processus, fournie par DeFiSafety. DefiSafety est un service public non officiel qui publie des avis sur diverses DApps Ethereum publiques de grande taille. Une partie du système d'évaluation de DeFiSafety comprend la façon dont le projet adhère à cette liste de contrôle qualité des processus. En suivant ces processus :
- vous produirez du code plus sécurisé, via des tests reproductibles et automatisés ;
- les experts seront en mesure de vérifier votre projet plus efficacement ;
- l'intégration de nouveaux développeurs sera plus simple ;
- les développeurs pourront itérer, tester et obtenir des commentaires sur les modifications rapidement ;
- votre projet enregistrera probablement moins de régressions.
Attaques et vulnérabilités
Maintenant que vous écrivez du code Solidity en utilisant un processus de développement efficace, examinons quelques vulnérabilités courantes de Solidity pour voir ce qui peut mal tourner.
Réentrance
La réentrance est l'un des problèmes de sécurité les plus importants à considérer lors du développement de contrats intelligents. L'EVM ne pouvant pas exécuter plusieurs contrats en même temps, un contrat appelant un autre contrat met en pause l'exécution du contrat appelant et l'état de la mémoire jusqu'à ce que l'appel revienne, événement à partir duquel l'exécution reprend normalement. Cette pause et cette reprise peuvent créer une vulnérabilité connue sous le nom de "réentrance".
Voici une version simple d'un contrat vulnérable à la réentrance :
1// CE CONTRAT A UNE VULNÉRABILITÉ INTENTIONNELLE, NE PAS COPIER2contract Victim {3 mapping (address => uint256) public balances;45 function deposit() external payable {6 balances[msg.sender] += msg.value;7 }89 function withdraw() external {10 uint256 amount = balances[msg.sender];11 (bool success, ) = msg.sender.call.value(amount)("");12 require(success);13 balances[msg.sender] = 0;14 }15}16Afficher toutCopier
Pour permettre à un utilisateur de retirer l'ETH qu'il a précédemment stocké dans le contrat, cette fonction :
- lit le solde de l'utilisateur ;
- lui envoie le solde en ETH ;
- réinitialise le solde à 0, de sorte que l'utilisateur ne puisse pas retirer le solde de nouveau.
Si elle est appelée à partir d'un compte régulier (comme votre propre compte Metamask), cette fonction est comme attendue : msg.sender.call.value() envoie simplement l'ETH vers votre compte. Toutefois, les contrats intelligents peuvent également effectuer des appels. Si un contrat personnalisé et malveillant appelle la fonction withdraw()
, msg.sender.call.value() n'enverra pas le montant d'ETH (via amount
), mais appellera aussi implicitement le contrat pour commencer à exécuter du code. Imaginez le contrat malveillant suivant :
1contract 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}13Afficher toutCopier
Appeler Attacker.beginAttack() démarrera un cycle qui ressemble à quelque chose comme ça :
10.) Attacker's EOA calls Attacker.beginAttack() with 1 ETH20.) Attacker.beginAttack() deposits 1 ETH into Victim34 1.) Attacker -> Victim.withdraw()5 1.) Victim reads balanceOf[msg.sender]6 1.) Victim sends ETH to Attacker (which executes default function)7 2.) Attacker -> Victim.withdraw()8 2.) Victim reads balanceOf[msg.sender]9 2.) Victim sends ETH to Attacker (which executes default function)10 3.) Attacker -> Victim.withdraw()11 3.) Victim reads balanceOf[msg.sender]12 3.) Victim sends ETH to Attacker (which executes default function)13 4.) Attacker no longer has enough gas, returns without calling again14 3.) balances[msg.sender] = 0;15 2.) balances[msg.sender] = 0; (it was already 0)16 1.) balances[msg.sender] = 0; (it was already 0)17Afficher tout
Appeller Attacker.beginAttack avec 1 ETH génère une nouvelle attaque par réentrance contre la victime, retirant plus d'ETH qu'il n'en a été fourni (prélevé sur les soldes des autres utilisateurs, entraînant une sous-garantie du contrat de la victime)
Comment gérer la réentrance (de la mauvaise façon)
On pourrait envisager de contrecarrer la réentrance en empêchant simplement les contrats intelligents d'interagir avec votre code. Vous recherchez stackoverflow, vous trouvez cet extrait de code avec des tonnes de votes positifs :
1function isContract(address addr) internal returns (bool) {2 uint size;3 assembly { size := extcodesize(addr) }4 return size > 0;5}6Copier
Cela semble sensé : les contrats ont du code, si l'appelant a du code, cele ne l'autorise pas à déposer. Tentons de l'ajouter :
1// CE CONTRAT A UNE VULNÉRABILITÉ INTENTIONNELLE, NE PAS COPIER2contract ContractCheckVictim {3 mapping (address => uint256) public balances;45 function isContract(address addr) internal returns (bool) {6 uint size;7 assembly { size := extcodesize(addr) }8 return size > 0;9 }1011 function deposit() external payable {12 require(!isContract(msg.sender)); // <- NEW LINE13 balances[msg.sender] += msg.value;14 }1516 function withdraw() external {17 uint256 amount = balances[msg.sender];18 (bool success, ) = msg.sender.call.value(amount)("");19 require(success);20 balances[msg.sender] = 0;21 }22}23Afficher toutCopier
Maintenant pour déposer de l'ETH, vous ne devez pas avoir de code de contrat intelligent à votre adresse. Ceci est néanmoins facilement surmonté avec le contrat d'attaquant suivant :
1contract ContractCheckAttacker {2 constructor() public payable {3 ContractCheckVictim(VICTIM_ADDRESS).deposit(1 ether); // <- New line4 }56 function beginAttack() external payable {7 ContractCheckVictim(VICTIM_ADDRESS).withdraw();8 }910 function() external payable {11 if (gasleft() > 40000) {12 Victim(VICTIM_ADDRESS).withdraw();13 }14 }15}16Afficher toutCopier
Alors que la première attaque était une attaque sur la logique du contrat, il s'agit maintenant d'une attaque sur le comportement de déploiement du contrat Ethereum. Pendant la construction, un contrat n'a pas encore renvoyé le code à déployer à son adresse, mais conserve le contrôle complet de l'EVM DURANT ce processus.
Il est techniquement possible d'empêcher les contrats intelligents d'appeler votre code, en utilisant cette ligne :
1require(tx.origin == msg.sender)2Copier
Cependant, ce n'est toujours pas une bonne solution. L'un des aspects les plus passionnants d'Ethereum est sa composabilité : ses contrats intelligents s'intègrent et se construisent les uns avec les autres. En utilisant la ligne ci-dessus, vous limitez l'utilité de votre projet.
Comment gérer la réentrance (de la bonne façon)
En changeant simplement l'ordre de la mise à jour de stockage et de l'appel externe, nous empêchons la condition de réentrance qui a permis l'attaque. Un rappel vers un retrait, bien que possible, ne profitera pas à l'attaquant puisque le stockage des soldes
sera déjà défini à 0.
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}9Copier
Le code ci-dessus suit le modèle de conception "Checks-Effects-Interactions", qui aide à se protéger contre la réentrance. En savoir plus sur le modèle Checks-Effects-Interactions
Comment gérer la réentrance (l'option nucléaire)
Chaque fois que vous envoyez de l'ETH à une adresse non fiable ou que vous interagissez avec un contrat inconnu (comme appeler le "transfer()
" d'une adresse de jeton fournie par l'utilisateur), vous vous exposez à un risque de réentrance. En concevant des contrats qui n'envoient pas d'ETH et n'appellent pas de contrats non fiables, vous empêchez la possiblité de réentrance !
Autres types d'attaques
Les types d'attaque ci-dessus couvrent les problèmes de codage de contrats intelligents (réentrance) et les anomalies Ethereum (exécution de code dans les constructeurs de contrats, avant que le code ne soit disponible à l'adresse du contrat). Il existe beaucoup, beaucoup d'autres types d'attaque à connaître, y compris :
- Front running
- Rejet d'envoi d'ETH
- Dépassement/sous-flux d'entier
Complément d'information:
- Consensys Smart Contract Known Attacks - Une explication très lisible des vulnérabilités les plus importantes, avec un exemple de code pour la plupart.
- Registre SWC - Liste conservée des CWE qui s'appliquent à Ethereum et aux contrats intelligents
Outils de sécurité
Bien qu'il n'y ait pas de substitut à la compréhension des bases de sécurité d'Ethereum et à l'engagement d'un expert pour revoir votre code, il existe de nombreux outils disponibles pour aider à mettre en évidence les problèmes potentiels présents dans votre code.
Sécurité des contrats intelligents
Slither - Framework d'analyse statique Solidity rédigé en Python 3
MythX - API d'analyse de sécurité pour les contrats intelligents Ethereum
Mythril - Outil d'analyse de sécurité pour le bytecode de l'EVM
SmartContract.Codes - Moteur de recherche pour les codes sources Solidity vérifiés
Manticore - Interface en ligne de commande qui utilise un outil d'exécution symbolique sur les contrats intelligents et les fichiers binaires
Securify - Scanner de sécurité pour les contrats intelligents Ethereum
ERC20 Verifier - Outil utilisé pour vérifier qu'un contrat est conforme à la norme ERC20
Vérification formelle
Informations sur la vérification formelle
- How formal verification of smart-contacts works - Brian Marick, 20 juillet 2018
- How Formal Verification Can Ensure Flawless Smart Contracts 29 - Bernard Mueller, janvier 2018
Utilisation d'outils
Deux des outils les plus populaires pour l'analyse de sécurité des contrats intelligents sont :
- Slither par Trail of Bits (version hébergée : Crytic)
- Mythril par ConsenSys (version hébergée : MythX)
Tous deux sont des outils utiles qui analysent votre code et signalent les problèmes. Chacun possède une version hébergée par [commercial], mais est également disponible gratuitement pour fonctionner localement. Voici un bref exemple de la façon d'exécuter Slither, disponible dans une image Docker pratique trailofbits/eth-security-toolbox
. Vous devrez installer Node.js si vous ne l’avez pas déjà fait.
$ mkdir test-slither$ curl https://gist.githubusercontent.com/epheph/460e6ff4f02c4ac582794a41e1f103bf/raw/9e761af793d4414c39370f063a46a3f71686b579/gistfile1.txt > bad-contract.sol$ docker run -v `pwd`:/share -it --rm trailofbits/eth-security-toolboxdocker$ cd /sharedocker$ solc-select 0.5.11docker$ slither bad-contract.sol
génèrera cette sortie :
ethsec@1435b241ca60:/share$ slither bad-contract.solINFO:Detectors:Reentrancy in Victim.withdraw() (bad-contract.sol#11-16):External calls:- (success) = msg.sender.call.value(amount)() (bad-contract.sol#13)State variables written after the call(s):- balances[msg.sender] = 0 (bad-contract.sol#15)Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilitiesINFO:Detectors:Low level call in Victim.withdraw() (bad-contract.sol#11-16):- (success) = msg.sender.call.value(amount)() (bad-contract.sol#13)Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#low-level-callsINFO:Slither:bad-contract.sol analyzed (1 contracts with 46 detectors), 2 result(s) foundINFO:Slither:Use https://crytic.io/ to get access to additional detectors and GitHub integrationAfficher tout
Slither a identifié le potentiel de réentrance, ainsi que les lignes clés où le problème pourrait se produire, et nous donne un lien pour plus de détails sur le problème ici :
Reference : https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities
Ceci vous permet de connaître rapidement les problèmes potentiels dans votre code. Comme tous les outils de test automatisés, Slither n'est pas parfait, et peut signaler trop d'erreurs dans le rapport. Il peut avertir d'une éventuelle réentrance, même si aucune vulnérabilité exploitable n'existe. Souvent, l'examen des DIFFERENCES entre les changements de code répertoriées dans le rapport Slither est extrêmement utile, car il aide à découvrir les vulnérabilités introduites beaucoup plus tôt que si vous attendez la fin de votre projet.
Complément d'information
Guide des bonnes pratiques de sécurité pour les contrat intelligents
- consensys.github.io/smart-contract-best-practices/
- GitHub
- Compilation de recommandations de sécurité et de bonnes pratiques
Norme de vérification de la sécurité des contrats intelligents (SCSVS)
Une ressource communautaire vous a aidé ? Modifiez cette page et ajoutez-la !
Tutoriels connexes
- Flux de développement sécurisé
- Comment utiliser Slither pour trouver les bogues des contrats intelligents
- Comment utiliser Manticore pour trouver les bogues des contrats intelligents
- Consignes de sécurité
- Sécurité des jetons