Sécurité des contrats intelligents : les pièges qui coûtent le plus cher, et les parades

Un contrat intelligent exécute ce qui est écrit, y compris ce qui est mal écrit, et il le fait de façon irréversible. Tour d'horizon des failles qui reviennent le plus souvent, de ce que les grands incidents ont appris au secteur, et de ce qu'un audit ne couvre pas.

Voir la table des matières Ne plus voir la table des matières

Un contrat intelligent est un programme déposé sur une blockchain, qui s’exécute automatiquement lorsque des conditions inscrites à l’avance sont réunies. Personne ne l’active à la main, personne ne l’arbitre, et son résultat ne dépend d’aucune interprétation. C’est ce qui en fait un outil puissant pour organiser un échange entre deux parties qui ne se connaissent pas.

C’est aussi ce qui en fait un objet redoutable. Un contrat exécute ce qui est écrit, y compris ce qui est mal écrit, et il le fait sur des fonds bien réels et de façon irréversible. Le secteur en a fait l’expérience à répétition : Chainalysis estimait au 2 août 2022 que 2 milliards de dollars avaient déjà été dérobés dans treize attaques visant des ponts inter-chaînes, soit 69 % des fonds volés depuis le début de l’année.

La difficulté n’est donc pas de comprendre à quoi sert un contrat intelligent, mais de savoir où il casse. Quels sont les défauts qui reviennent, que peut-on en attendre d’un audit, et que reste-t-il à vérifier soi-même avant de confier des fonds à un programme ?

Ce qu’un contrat intelligent fait, et ce qu’il ne fait pas

Un programme, pas un contrat au sens juridique

L’expression prête à confusion. Un contrat intelligent n’est pas un document opposable devant un tribunal : c’est du code déployé à une adresse publique, qui manipule des soldes. Le terme a été forgé par le juriste et informaticien Nick Szabo au milieu des années 1990, bien avant qu’une blockchain ne permette de l’implémenter.

La conséquence pratique est directe. Là où un contrat classique laisse place à la négociation, à l’avenant et au juge, le contrat intelligent ne connaît que l’état de son code au moment de l’appel. Une clause mal rédigée dans un contrat papier se corrige ; une fonction mal écrite dans un contrat déployé s’exploite.

Trois propriétés qui changent le rapport au risque

Le déterminisme vient en premier : pour un même état et une même entrée, le contrat produit toujours la même sortie, sur tous les nœuds du réseau. Cette propriété rend le résultat vérifiable par n’importe qui, mais elle signifie aussi qu’une erreur se reproduit à l’identique à chaque appel, sans jamais s’atténuer.

L’immuabilité vient ensuite. Un contrat déployé ne se modifie pas, sauf à avoir prévu un mécanisme de mise à jour, lequel introduit à son tour ses propres risques. La transparence complète la série : le code est public, souvent vérifié sur un explorateur de blocs, et l’attaquant lit exactement la même chose que le développeur. Aucune sécurité par l’obscurité n’est disponible ici.

Ces trois propriétés expliquent pourquoi une faille de contrat intelligent se solde rarement par un incident mineur. La combinaison d’un code public, d’un comportement prévisible et d’une exécution irrévocable transforme le moindre écart de logique en opportunité économique immédiate, chiffrable à la ligne près par quiconque sait lire.

Les vulnérabilités qui reviennent le plus souvent

Les catégories de défauts sont connues, documentées et recensées dans des référentiels publics depuis des années. Ce qui change d’une année sur l’autre, ce n’est pas la nature des failles, mais les montants exposés au moment où elles sont trouvées. Voici celles qui reviennent le plus dans les rapports d’incident.

  • La réentrance, quand un contrat appelle une adresse extérieure qui le rappelle avant qu’il ait mis son état à jour ;
  • le contrôle d’accès défaillant, quand une fonction sensible n’est protégée par aucune vérification de l’appelant ;
  • les dépassements d’entier, quand une opération arithmétique franchit la borne du type utilisé et repart à zéro ;
  • les appels externes dont la valeur de retour n’est jamais vérifiée, ce qui laisse le contrat croire à un succès ;
  • la dépendance à une source de prix unique, manipulable le temps d’une transaction ;
  • l’exploitation de l’ordre des transactions, un observateur payant plus cher pour passer avant vous.

Chacune mérite un mot, parce que les parades diffèrent et ne se placent pas au même endroit du code.

La réentrance

Le schéma est toujours le même. Un contrat envoie des fonds à une adresse avant d’avoir décrémenté le solde interne de l’appelant. Si cette adresse est elle-même un contrat, elle peut rappeler la fonction de retrait dans la foulée, encore et encore, tant que le solde affiché n’a pas été mis à jour. C’est la faille qui a vidé The DAO en juin 2016.

La parade tient en une discipline d’écriture : vérifier, puis modifier l’état interne, puis seulement interagir avec l’extérieur. Un verrou de non-réentrance, comme celui fourni par les bibliothèques éprouvées du secteur, ajoute une seconde ligne de défense. L’ordre des instructions est ici une mesure de sécurité, pas une préférence stylistique.

Le contrôle d’accès défaillant

Une fonction d’émission de jetons, de retrait d’urgence ou de changement de propriétaire doit vérifier qui l’appelle. Quand cette vérification manque, ou qu’elle porte sur la mauvaise variable, n’importe quelle adresse peut déclencher l’opération. Le gel de plus de 513 000 ethers dans les portefeuilles multisignature de Parity, en novembre 2017, relève de cette famille : une bibliothèque partagée non initialisée a pu être appropriée puis détruite par un tiers.

La difficulté n’est pas technique, elle est organisationnelle. Sur un contrat de quelques milliers de lignes, l’inventaire des fonctions privilégiées se perd vite, ainsi que celui de leurs conditions d’appel. Le tenir à jour, explicitement, dans la documentation du projet, coûte peu et rattrape beaucoup.

Les dépassements d’entier

Avant décembre 2020, une soustraction ramenant un entier non signé sous zéro produisait silencieusement un nombre gigantesque, ce qui permettait de fabriquer des soldes à partir de rien. La version 0.8.0 du langage Solidity a rendu ces contrôles automatiques, ce qui a fait disparaître l’essentiel du problème sur les contrats récents. Les contrats plus anciens restent exposés, et beaucoup tournent encore.

Les appels externes non vérifiés

Certaines primitives d’envoi de fonds renvoient un booléen au lieu d’interrompre l’exécution en cas d’échec. Un contrat qui ignore cette valeur poursuit son cheminement comme si le transfert avait réussi, et enregistre un état qui ne correspond plus à la réalité des soldes. Le correctif consiste à tester systématiquement le retour, ou à employer une primitive qui échoue bruyamment.

La dépendance à un oracle de prix

Un contrat de prêt a besoin de connaître la valeur du collatéral déposé. S’il lit ce prix sur une seule réserve de liquidité, un attaquant peut emprunter massivement le temps d’un bloc, déséquilibrer cette réserve, faire évaluer sa position au prix déformé, puis tout rembourser. L’emprunt remboursé dans la même transaction rend cette manœuvre accessible sans capital de départ, ce qui a alimenté une longue série d’attaques sur des protocoles de prêt depuis 2020.

Les parades existent et sont connues : agréger plusieurs sources, utiliser des moyennes pondérées dans le temps, refuser les mouvements de prix aberrants sur un seul bloc. Elles coûtent en complexité et en frais de transaction, ce qui explique qu’elles soient encore régulièrement écartées.

L’ordre des transactions

Une transaction en attente est publique avant d’être confirmée. Un observateur peut la lire, en soumettre une autre avec des frais supérieurs, et se placer devant. Sur un échange décentralisé, ce mécanisme se traduit par un prix d’exécution dégradé pour l’utilisateur. La tolérance de glissement, les enchères par lots et les canaux de soumission privés limitent l’exposition sans la supprimer.

Ce que les grands incidents ont appris au secteur

Les pertes marquantes de ces dernières années ne relèvent pas toutes de la même cause, et c’est précisément l’enseignement. Le tableau ci-dessous rassemble les épisodes les plus documentés et l’origine de la faille exploitée dans chacun.

IncidentDateOrdre de grandeurOrigine de la faille
The DAOJuin 20163,6 millions d’ethersRéentrance dans la fonction de retrait
Portefeuilles ParityNovembre 2017513 000 ethers gelésBibliothèque partagée non initialisée
Poly NetworkAoût 2021611 millions de dollarsContrôle d’accès inter-chaînes
WormholeFévrier 2022326 millions de dollarsVérification de signature contournée
RoninMars 2022624 millions de dollarsClés de validateurs compromises
NomadAoût 2022190 millions de dollarsInitialisation rendant toute preuve valide

Une lecture saute aux yeux : la moitié de ces montants ne vient pas d’un bug de logique métier, mais d’un problème de clés, de signatures ou d’initialisation. Le code applicatif n’est pas toujours le maillon faible, et le concentrer seul dans le périmètre de vérification laisse la porte ouverte ailleurs.

Le second constat porte sur la concentration des réserves dans les ponts. Les infrastructures qui relient deux blockchains concentrent des réserves considérables en un point unique, ce qui en fait des cibles de choix. L’arrêt d’urgence de BNB Chain en octobre 2022, après la falsification d’une preuve sur son pont, a montré qu’un réseau entier pouvait être suspendu pour contenir une exploitation.

L’audit : ce qu’il couvre, ce qu’il ne couvre pas

Faire relire son code par un cabinet spécialisé reste la mesure la plus rentable qu’un projet puisse prendre. Un audit sérieux met au jour des défauts qu’une équipe interne, familière de son propre code, ne voit plus. Il ne transforme pas pour autant un contrat en objet sûr, et l’écart entre ces deux propositions est la source de beaucoup de malentendus.

Un audit est une revue datée, portant sur une version donnée du code, dans un périmètre négocié. Il ne couvre ni les modifications apportées après la remise du rapport, ni la façon dont les clés de déploiement sont gardées, ni la chaîne de compilation, ni les dépendances installées la veille de la mise en production. Les outils automatisés ne comblent pas ce vide : une étude publiée par Trail of Bits a mesuré qu’ils détectaient environ la moitié des vulnérabilités identifiées lors de revues manuelles.

Ce n’est pas une bonne chose qu’il existe une dépendance à des consultants externes pour une compétence fondamentale nécessaire à la construction de logiciels blockchain.

Dan Guido, fondateur du cabinet de sécurité Trail of Bits, cité par CyberScoop dans un article publié le 26 juillet 2022

Le rapport d’audit lui-même mérite d’être lu, et pas seulement brandi. Un point classé comme un simple risque de confiance, parce qu’il décrit des pouvoirs d’administration très étendus plutôt qu’un bug, reste un point de défaillance parfaitement exploitable. Le nombre de projets ayant accusé réception d’une telle remarque sans rien changer à leur architecture est considérable.

Écrire un contrat plus difficile à casser

Choisir un langage et fixer sa version

Solidity domine largement l’écosystème compatible avec la machine virtuelle Ethereum, avec Vyper comme alternative volontairement plus restrictive. Le choix compte moins que la discipline de version : figer explicitement la version du compilateur évite qu’un contrat écrit pour une sémantique donnée ne soit compilé avec une autre, ce qui a déjà produit des surprises coûteuses.

Réutiliser des briques éprouvées

Réécrire un jeton fongible, un contrôle de propriété ou un verrou de réentrance revient à réintroduire des bugs déjà corrigés ailleurs. Les bibliothèques de référence du secteur sont lues, testées et attaquées depuis des années par des milliers de développeurs. Le code le plus sûr reste celui qu’on n’écrit pas, à condition d’en épingler la version et de suivre les avis de sécurité publiés.

Tester au-delà du cas nominal

Une couverture de tests élevée sur les scénarios attendus ne dit rien de la robustesse. Les cas qui comptent sont ceux qu’on n’a pas prévus : montants nuls, montants extrêmes, appelants qui sont eux-mêmes des contrats, séquences d’appels dans un ordre inhabituel. Les réseaux de test permettent de rejouer ces scénarios sans exposer un seul jeton réel, et rien n’oblige à s’en priver.

Outiller l’analyse

L’analyse statique repère les motifs dangereux sans exécuter le code, tandis que le test aléatoire guidé cherche à violer des propriétés que le développeur a écrites lui-même. Le second exercice a une vertu inattendue : formuler la propriété à vérifier oblige à dire ce que le contrat est censé garantir, et cette formalisation révèle à elle seule des angles morts.

La gouvernance du contrat compte autant que son code

Clés, signature multiple et délais

Un contrat parfaitement écrit reste à la merci de la clé qui l’administre. Confier la mise à jour ou l’émission à une clé unique, détenue par une seule personne sur une machine de développement, revient à ramener toute la sécurité du protocole au niveau de ce poste de travail. La signature multiple répartit ce pouvoir, et le délai de verrouillage impose une fenêtre publique avant qu’un changement ne prenne effet.

Cette fenêtre est le vrai apport du dispositif. Elle ne rend pas l’attaque impossible, elle donne le temps de voir venir une modification suspecte et de retirer ses fonds. Sans délai, une prise de contrôle des clés se traduit par une vidange immédiate.

Proxys et mises à jour

Rendre un contrat modifiable résout un problème et en crée un autre. Le schéma de délégation, où une façade immuable pointe vers une implémentation remplaçable, permet de corriger un défaut sans migrer les fonds. Il place aussi un pouvoir de réécriture totale entre les mains de l’administrateur, et transforme la question de la sécurité en question de gouvernance.

Les collisions de stockage entre l’ancienne et la nouvelle implémentation constituent le piège technique classique de ce schéma. Les conventions d’emplacement de stockage adoptées par l’écosystème existent précisément pour l’éviter, et les ignorer produit des corruptions d’état difficiles à diagnostiquer.

Primes aux bugs et procédures d’urgence

Une prime à la découverte de vulnérabilité coûte une fraction du montant en jeu et mobilise des chercheurs bien au-delà de l’équipe d’audit. Les plateformes spécialisées affichent des récompenses atteignant plusieurs millions de dollars pour les défauts critiques, montant dérisoire au regard des centaines de millions perdus dans les incidents recensés plus haut.

La procédure d’urgence complète le dispositif. Savoir à l’avance qui peut mettre le protocole en pause, à quelles conditions et par quel canal, fait la différence entre une perte contenue et une perte totale. Les minutes qui suivent la détection d’une exploitation ne se prêtent pas à l’improvisation.

Ce qu’un utilisateur peut vérifier avant de déposer des fonds

Un particulier ne relira pas le code d’un protocole avant d’y placer une somme. Il peut en revanche regarder ce qui entoure ce code, et ces éléments sont publics. L’existence d’un rapport d’audit récent, le nom du cabinet, la date de la revue et surtout la liste des points restés sans correction se trouvent en quelques minutes.

Le second réflexe porte sur l’étendue des pouvoirs d’administration. Qui peut mettre à jour le contrat, combien de signatures sont nécessaires, existe-t-il un délai avant application ? Un protocole qui documente clairement ces réponses n’est pas nécessairement sûr, mais un protocole qui les élude a déjà répondu. Les signaux d’alerte de la finance décentralisée se lisent souvent dans ce qui n’est pas écrit.

Reste la question de la taille. Un contrat qui vient d’être déployé, quelle que soit la qualité de sa revue, n’a pas encore subi l’épreuve du temps ni celle du montant immobilisé, et cette épreuve n’a aucun équivalent en laboratoire. Répartir ses positions et accepter qu’une part du risque soit technique plutôt que financière relève moins de la prudence que de la description exacte de ce qu’on achète.

Le secteur progresse, plus lentement qu’il ne se développe. Chaque nouvelle catégorie de produit, des ponts inter-chaînes aux jetons de jalonnement liquide, ouvre une surface d’attaque que les référentiels existants n’avaient pas prévue. La question ouverte n’est pas de savoir si ces failles seront trouvées, mais si les protocoles concernés auront prévu, avant ce jour-là, comment ils comptent réagir.

Associé à : , , , ,


Vous appréciez cet article ?

Partagez-le avec vos proches Ajoutez-nous comme source préférée