Les vulnérabilités les plus courantes des smart contracts sont la réentrance, les failles de contrôle d'accès, la manipulation d'oracle et de prix, le front-running, les erreurs de calcul et d'arrondi, les appels externes non vérifiés, les erreurs de mise à jour, le déni de service, le rejeu de signature et la gestion des clés. La plupart relèvent d'une logique ordinaire qui se comporte autrement dès qu'un attaquant peut l'appeler avec n'importe quelle entrée, dans n'importe quel ordre. Ce guide présente chacune d'elles, son impact typique et sa prévention.
Les dix vulnérabilités en un coup d'œil
Le tableau résume les dix familles d'erreurs que les auditeurs recherchent méthodiquement.
| Vulnérabilité | Ce qui se passe | Impact typique |
|---|---|---|
| Réentrance | Un appel externe revient dans le contrat avant la mise à jour de son état | Fonds retirés plusieurs fois |
| Contrôle d'accès | Une fonction sensible est appelable par le mauvais compte | Prise de contrôle du contrat ou des fonds |
| Manipulation d'oracle et de prix | Le contrat se fie à un prix modifiable dans la même transaction | Prêts ou échanges à un faux prix |
| Front-running et MEV | Des tiers voient et réordonnent les transactions en attente | Prix dégradés pour les utilisateurs |
| Calcul et arrondi | Troncature, dépassement ou arrondi dans le mauvais sens | Fuite progressive de valeur, comptabilité faussée |
| Appels externes non vérifiés | Un appel échoué est traité comme réussi | État et soldes divergent |
| Mise à jour et initialisation | Proxy ou initialiseur mal configuré | Prise de contrôle ou perte définitive du contrat |
| Déni de service | Une fonction peut être bloquée pour tous | Fonds ou fonctionnalités gelés |
| Rejeu de signature | Une signature valide est acceptée plusieurs fois ou ailleurs | Actions répétées ou non autorisées |
| Clés et centralisation | Trop de pouvoir repose sur une clé ou une partie | Perte totale en cas de compromission |
Le Smart Contract Top 10 de l'OWASP propose une classification complémentaire, que de nombreuses équipes utilisent comme liste de contrôle.
Vulnérabilités de logique et d'état
Ces vulnérabilités apparaissent quand la comptabilité interne du contrat peut s'écarter de la réalité, le plus souvent autour des appels externes et des calculs.
Réentrance
La réentrance survient quand un contrat appelle une adresse externe avant d'avoir fini de mettre à jour son propre état, et que cette adresse rappelle le contrat. Le second appel voit l'ancien état, par exemple un solde pas encore diminué.
- Impact typique : les mêmes fonds sont retirés à répétition jusqu'à vider le contrat. Des variantes existent entre plusieurs fonctions ou contrats partageant un état.
- Prévention : appliquer le schéma checks-effects-interactions (vérifications, puis mise à jour de l'état, puis appels externes), ajouter un verrou anti-réentrance sur les fonctions qui déplacent de la valeur, et traiter tout appel externe comme un point où le contrôle quitte votre contrat.
Illustration minimale de l'ordre sûr :
function withdraw(uint256 amount) external nonReentrant {
require(balances[msg.sender] >= amount, "Insufficient balance"); // checks
balances[msg.sender] -= amount; // effects
(bool ok, ) = msg.sender.call{value: amount}(""); // interactions
require(ok, "Transfer failed");
}
Erreurs de calcul et d'arrondi
Ces erreurs surviennent quand un calcul produit un résultat inattendu. Les versions récentes de Solidity annulent la transaction en cas de dépassement, mais les blocs explicitement non vérifiés, les conversions de type, les anciens compilateurs et d'autres langages restent exposés. L'arrondi est aujourd'hui le problème le plus fréquent : la division entière tronque toujours, et le sens de l'arrondi décide qui absorbe l'écart.
- Impact typique : de petites erreurs répétées de nombreuses fois, un prix de part gonflable dans un coffre vide, ou une comptabilité qui ne correspond plus au solde réel.
- Prévention : arrondir en faveur du protocole, multiplier avant de diviser, documenter la précision de chaque valeur et tester des invariants comme « le total des parts correspond toujours au total des actifs » par fuzzing (tests automatisés par entrées aléatoires).
Appels externes non vérifiés
Un appel externe non vérifié est un appel dont l'échec est ignoré. Les appels de bas niveau renvoient un indicateur de succès au lieu d'annuler la transaction, et certains tokens renvoient « false » au lieu d'échouer.
- Impact typique : le contrat enregistre un paiement qui n'a jamais eu lieu, et son état diverge des soldes réels.
- Prévention : toujours vérifier les valeurs de retour, utiliser des fonctions de transfert sécurisées éprouvées, et décider explicitement du comportement en cas d'échec.
Vulnérabilités de permissions et de gouvernance
Ces vulnérabilités apparaissent quand la mauvaise partie peut effectuer une action sensible, ou quand la bonne partie détient trop de pouvoir.
Failles de contrôle d'accès
Une faille de contrôle d'accès survient quand une fonction qui devrait être restreinte (émission de tokens, pause, modification de paramètres, retrait de frais) n'a pas de vérification ou vérifie la mauvaise condition. Causes fréquentes : un modificateur oublié, un initialiseur public, ou le recours à l'origine de la transaction plutôt qu'à l'appelant direct.
- Impact typique : un attaquant émet des tokens, modifie des paramètres critiques ou s'approprie le contrat.
- Prévention : établir une carte écrite des rôles et de leurs fonctions, utiliser des bibliothèques de gestion des rôles reconnues, tester chaque fonction restreinte depuis un compte non autorisé et limiter le nombre de fonctions privilégiées.
Erreurs de mise à jour et d'initialisation
Les contrats évolutifs séparent le stockage (le proxy) de la logique (l'implémentation), ce qui crée de nouveaux modes de défaillance. Un proxy évolutif peut subir des collisions d'emplacements de stockage entre versions, une implémentation non initialisée que n'importe qui peut revendiquer, ou une fonction de mise à jour mal protégée.
- Impact typique : prise de contrôle, stockage corrompu après une mise à jour, ou implémentation détruite qui rend le proxy inutilisable.
- Prévention : utiliser un schéma de proxy standard, initialiser dans la transaction de déploiement, désactiver les initialiseurs de l'implémentation, contrôler la disposition du stockage entre versions avec un outil, et placer les mises à jour derrière un multisig et un timelock (délai obligatoire avant exécution).
Gestion des clés et centralisation
Le risque de centralisation existe quand une clé ou un petit groupe peut déplacer des fonds, changer les règles ou mettre à jour le code sans délai. Ce n'est pas un bug de code, mais les auditeurs le signalent car les utilisateurs en subissent les conséquences.
- Impact typique : perte totale si une clé d'administration est volée, hameçonnée ou mal utilisée, et perte de confiance même en l'absence d'incident.
- Prévention : utiliser un multisig avec des signataires indépendants, un timelock sur les changements sensibles pour laisser aux utilisateurs le temps de réagir, des clés sur supports matériels et un plan de réduction progressive des privilèges.
Vulnérabilités de marché et d'ordonnancement
Ces vulnérabilités tiennent au fait que les transactions sont publiques avant exécution et que les prix sur la blockchain peuvent être déplacés.
Manipulation d'oracle et de prix
Un oracle fournit des données externes, le plus souvent des prix. Si un contrat lit directement le prix d'une seule réserve de liquidité, n'importe qui peut le déplacer le temps d'une transaction. Le flash loan rend cela peu coûteux : il fournit un capital important à rembourser dans la même transaction, l'attaquant ne payant que des frais.
- Impact typique : emprunt contre une garantie surévaluée, liquidation de positions saines ou échange à un faux prix.
- Prévention : utiliser des oracles décentralisés ou des prix moyens pondérés dans le temps plutôt que des prix instantanés, contrôler la fraîcheur et la plausibilité de chaque valeur, et définir le comportement du protocole en cas de défaillance de l'oracle.
Front-running et MEV
Le front-running consiste à voir une transaction en attente et à placer la sienne avant. La MEV (valeur extractible maximale) désigne plus largement la valeur que producteurs de blocs et robots spécialisés captent en ordonnant, insérant ou excluant des transactions.
- Impact typique : exécution dégradée des échanges, prise en sandwich, ou perte du bénéfice d'une action comme la réclamation d'une récompense ou la révélation d'une enchère.
- Prévention : permettre aux utilisateurs de fixer une tolérance de glissement et une échéance, recourir à des schémas d'engagement puis révélation quand le secret compte, et éviter les mécanismes où le premier appelant capte la valeur.
Vulnérabilités de disponibilité et d'authentification
Ces vulnérabilités ne volent pas toujours directement des fonds, mais elles peuvent geler un protocole ou autoriser des actions que personne n'a voulues.
Déni de service
Un déni de service fait échouer une fonction pour tous les utilisateurs. Causes classiques : des boucles sur des listes qui grossissent sans limite jusqu'à dépasser la limite de gas d'un bloc, et des paiements envoyés à de nombreux destinataires dont un seul, en échec, bloque tous les autres.
- Impact typique : retraits, liquidations ou gouvernance deviennent impossibles, parfois définitivement.
- Prévention : borner la taille des boucles, laisser chaque utilisateur retirer ses fonds plutôt que de les lui envoyer, et vérifier qu'aucun tiers ne peut bloquer un chemin critique.
Rejeu de signature
Le rejeu survient quand un message signé est accepté plusieurs fois, sur un autre contrat ou sur une autre blockchain. Il concerne les autorisations signées, les méta-transactions et les carnets d'ordres hors chaîne.
- Impact typique : une action approuvée est exécutée plusieurs fois, ou réutilisée là où le signataire ne l'avait pas prévu.
- Prévention : inclure un nonce, une date d'expiration, l'adresse du contrat et l'identifiant de la chaîne dans chaque message signé, suivre la norme de signature de données typées, et marquer chaque signature comme utilisée.
Réduire ces risques en pratique
Réduire ces risques demande plusieurs niveaux de défense, car aucune technique ne détecte toutes les familles de vulnérabilités. Un processus solide combine :
- Une revue de conception avant le code, pour arrêter délibérément les rôles, les sources d'oracle et la politique de mise à jour.
- Des tests couvrant les fonctions restreintes, les chemins d'échec et les valeurs limites, complétés par du fuzzing et des tests d'invariants.
- L'analyse statique en intégration continue pour repérer tôt les schémas connus.
- Un audit indépendant sur un commit figé, suivi d'un réaudit des corrections. Le guide du processus d'audit en décrit chaque étape.
- Un bug bounty (programme de récompense pour la découverte de failles) et une surveillance après le lancement.
Plusieurs de ces vulnérabilités pèsent aussi sur l'effort d'audit. Dans le modèle indicatif du site, les intégrations externes et les contrats évolutifs ajoutent chacun 10 % et les calculs sur mesure 20 % aux jours-auditeur estimés. Le calculateur de coût d'audit montre comment ces facteurs se combinent pour votre code ; les devis réels dépendent du périmètre effectif.
Un audit réduit le risque mais ne garantit pas l'absence de bugs. La sécurité est une discipline continue, pas un certificat.
À retenir
- La plupart des vulnérabilités courantes viennent d'une logique ordinaire exposée à des appelants hostiles : ordre des appels, calculs, permissions et données de confiance.
- Checks-effects-interactions, valeurs de retour vérifiées et boucles bornées préviennent une grande part des problèmes de logique et de disponibilité.
- Prix instantanés, initialiseurs non protégés et clé d'administration unique font partie des choix que les auditeurs examinent en premier.
- Combinez tests, fuzzing, analyse statique, audit indépendant, réaudit et bug bounty, car chacun détecte des problèmes différents.
- Aucun audit ne garantit un code sans bug ; il réduit le risque sur un périmètre précis et figé.