Liste des vulnérabilités courantes
Les vulnérabilités des contrats intelligents peuvent entraîner des conséquences catastrophiques et souvent irréversibles une fois qu'ils sont déployés. L'exploitation de ces faiblesses représente un défi majeur pour la sécurité de la blockchain, entraînant le vol de milliards de dollars d'actifs et sapant la confiance des utilisateurs. Assurer une sécurité robuste des contrats intelligents est donc primordial. Cela exige plus qu'un simple déploiement de code sur la blockchain. Cela nécessite des pratiques rigoureuses de codage sécurisé, des tests approfondis et, souvent, des audits indépendants de contrats intelligents pour identifier les problèmes avant qu'ils ne puissent être exploités. Comprendre les pièges courants – des attaques par réentrance comme le piratage tristement célèbre de la DAO aux erreurs logiques subtiles et aux vulnérabilités d'Ethereum telles que les dépassements d'entiers – constitue la première étape cruciale vers l'atténuation des risques. Cette page fournit un aperçu essentiel des vulnérabilités fréquentes des contrats intelligents afin de renforcer la sensibilisation et de promouvoir un développement plus sûr dans l'écosystème décentralisé.
- Reentrancy
- Unexpected Ether
- DoS
- Overflow
- tx.origin Authentication
- Access Control
- Weak Randomness
- Hash Collision
- Precision Loss
- msg.value in loop
- ecrecover
- Replay
- Inheritance Order
- MEV
La réentrance est l'une des vulnérabilités les plus anciennes et les plus dévastatrices des contrats intelligents, responsable d'exploits historiques majeurs comme le piratage de The DAO, qui a conduit à un hard fork d'Ethereum. Elle demeure une menace critique aux conséquences potentiellement terribles, capable de vider les contrats de tous leurs fonds.
La vulnérabilité découle d'un anti-patron de codage courant, souvent hérité des habitudes du Web2 : effectuer des interactions externes (comme transférer de l'Ether ou appeler un autre contrat) avant de mettre à jour l'état interne du contrat (par exemple, les soldes des utilisateurs). Lorsqu'un contrat envoie de l'Ether ou appelle un contrat externe, il transfère temporairement le contrôle de l'exécution. Un attaquant contrôlant le contrat destinataire peut exploiter cela en rappelant immédiatement le contrat d'origine, souvent via la fonction receive ou fallback déclenchée par le transfert d'Ether, ou par le biais des hooks des standards de jetons.
Comme l'état du contrat d'origine (par exemple, le solde de l'attaquant) n'a pas encore été mis à jour, l'appel réentrant passe les vérifications initiales, permettant à l'attaquant de répéter l'action (comme retirer des fonds) plusieurs fois au sein de la même transaction. Cette boucle récursive se poursuit jusqu'à ce que les fonds soient épuisés ou que les limites de gaz soient atteintes.
Bien que l'attaque classique implique une seule fonction, des variations plus complexes existent, telles que la réentrance inter-fonctions (exploitant un état partagé entre les fonctions) et la réentrance en lecture seule (manipulant les lectures d'état via des fonctions view).
La défense principale est le patron Checks-Effects-Interactions (CEI) : effectuer toutes les vérifications nécessaires, puis mettre à jour les variables d'état (effets), et seulement après ces mises à jour, interagir avec des contrats externes.
La vulnérabilité de "Manipulation du solde par réception inattendue d'Ether" survient car les contrats intelligents Solidity peuvent recevoir de l'Ether via des mécanismes qui contournent leurs fonctions `receive` et `fallback` définies. Les principales méthodes pour cette injection "inattendue" d'Ether sont l'opcode `selfdestruct`, qui transfère de force le solde d'un contrat en cours de destruction vers une adresse désignée, et les transactions coinbase (récompenses de bloc).
Le problème fondamental est que ces transferts forcés augmentent directement le solde Ether du contrat (`address(this).balance`) sans déclencher la logique programmée du contrat pour la gestion des fonds entrants. Cela brise une supposition ou un invariant courant, souvent implicite : que le solde réel du contrat reflète précisément la somme des fonds traités via ses fonctions `payable` prévues ou comptabilisés en interne.
Les contrats qui utilisent incorrectement `address(this).balance` pour des vérifications logiques critiques deviennent vulnérables. Par exemple, des contrats pourraient vérifier que `address(this).balance == montantAttendu`. Un attaquant peut exploiter cela en utilisant `selfdestruct` pour envoyer une petite quantité d'Ether, manipulant ainsi le solde. Cela peut entraîner :
Déni de Service (DoS) : Les vérifications d'égalité stricte peuvent être définitivement compromises, rendant les fonctions inutilisables.
Manipulation de la logique : Des seuils peuvent être atteints prématurément, déclenchant des changements d'état ou des paiements non intentionnels.
Violations d'assertion (`assert`) : Dans de rares cas, le solde inattendu peut entraîner des incohérences d'état internes qui provoquent l'échec d'un `assert()`, consommant tout le gaz de la transaction.
La mesure d'atténuation fondamentale est de ne jamais se fier à `address(this).balance` pour la logique du contrat. Au lieu de cela, les contrats doivent maintenir une comptabilité interne précise à l'aide de variables d'état dédiées, en les mettant à jour uniquement dans les fonctions légitimes de gestion des fonds. Toutes les vérifications critiques et les transitions d'état doivent être basées sur ces variables internes fiables.
Les vulnérabilités de Déni de Service (Denial of Service, DoS) dans les contrats intelligents Solidity visent à perturber ou à arrêter complètement la fonctionnalité prévue du contrat, empêchant les utilisateurs légitimes d'accéder à ses services. Le « service » refusé est la capacité du contrat à exécuter ses fonctions comme programmé et attendu par ses utilisateurs.
Les attaquants y parviennent en exploitant des failles dans la logique du code du contrat, en manipulant les limitations de ressources (principalement le gaz) ou en exploitant les dépendances externes.
Les schémas courants incluent :
Épuisement du Gaz (Gas Exhaustion) : La création de transactions ou la manipulation de l'état pour que les coûts d'exécution des fonctions dépassent la limite de gaz du bloc ou la limite de gaz de la transaction. Cela implique souvent le déclenchement d'opérations coûteuses en calcul ou, très couramment, l'itération sur des tableaux non bornés dont la taille peut croître indéfiniment. À mesure que les données augmentent, le coût en gaz de la boucle dépasse les limites, rendant la fonction inutilisable.
Reverts (Annulations) Inattendus : Provoquer l'annulation (revert) inattendue de fonctions critiques. Cela peut être déclenché en forçant l'échec d'appels externes (par exemple, envoyer des fonds à un contrat qui les rejette), en manipulant l'état pour faire échouer les conditions `require`, ou par d'autres exceptions non gérées. Si un contrat dépend d'un appel externe qui échoue (potentiellement induit de manière malveillante), l'opération entière peut être bloquée.
La nature immuable des contrats intelligents rend le DoS particulièrement grave. Une attaque réussie peut entraîner des fonds bloqués en permanence ou une fonctionnalité irrécupérable, car le code du contrat ne peut pas être facilement corrigé après le déploiement.
L'atténuation repose sur des pratiques de codage sécurisées : l'utilisation d'opérations bornées au lieu de boucles non bornées, la préférence pour les modèles de "pull-payment" (paiement par retrait) par rapport à l'envoi de fonds aux utilisateurs ("push-payment"), ce qui isole les échecs de transfert, la gestion robuste des échecs d'appels externes (par exemple, l'utilisation de `try/catch` lorsque cela est approprié) et une gestion attentive du gaz.
Les vulnérabilités de dépassement (overflow) et de sous-dépassement (underflow) d'entiers représentent une menace persistante dans le domaine de la sécurité des contrats intelligents (smart contracts), découlant des limitations fondamentales de l'arithmétique des entiers de taille fixe. Un dépassement se produit lorsque le résultat d'une opération arithmétique excède la valeur maximale pour son type, et un sous-dépassement lorsque le résultat tombe en dessous de la valeur minimale.
Historiquement, dans les versions de Solidity antérieures à la 0.8.0, ces erreurs arithmétiques provoquaient un "bouclage" silencieux de la valeur ("wrap around"). Par exemple, ajouter 1 à la valeur maximale de uint8 (255) donnait 0, et soustraire 1 de 0 donnait 255. Ce comportement prévisible mais non vérifié était une source majeure d'exploits, permettant aux attaquants de manipuler les soldes de jetons (par exemple, créer d'énormes quantités ou drainer des fonds en faisant boucler les soldes), de contourner des contrôles de sécurité critiques, de corrompre l'état du contrat ou de provoquer un déni de service (denial-of-service).
La version 0.8.0 de Solidity a introduit une amélioration de sécurité cruciale : les opérations arithmétiques standard (+, -, *, /) effectuent désormais des vérifications de dépassement et de sous-dépassement par défaut. Si une opération devait entraîner un bouclage, la transaction est annulée (reverts) à la place, empêchant la corruption silencieuse de l'état.
Cependant, le risque n'est pas entièrement éliminé. Les développeurs peuvent explicitement contourner ces vérifications par défaut en utilisant des blocs `unchecked`, souvent pour optimiser le gaz (gas optimization). Le code à l'intérieur d'un bloc `unchecked` revient au dangereux comportement de bouclage silencieux des versions antérieures à 0.8.0 et nécessite une validation extrêmement prudente. De même, le code assembleur EVM de bas niveau ne bénéficie pas des vérifications de Solidity, exigeant une gestion manuelle de la sécurité pour les opérations arithmétiques. De plus, les opérations de décalage (<<, >>) restent non vérifiées même en mode par défaut >=0.8.0 et tronqueront les résultats. Un transtypage (type casting) incorrect (par exemple, convertir un grand uint256 en un type plus petit comme uint8) peut également entraîner une troncature de valeur, causant potentiellement un comportement inattendu dans les calculs ultérieurs.
Par conséquent, bien que considérablement atténué par défaut dans le Solidity moderne, le dépassement/sous-dépassement d'entiers reste une vulnérabilité pertinente, en particulier dans les contrats hérités (legacy contracts), le code utilisant des blocs `unchecked` ou de l'assembleur, et à travers des opérations spécifiques non vérifiées ou des conversions de types négligentes.
L'utilisation de `tx.origin` pour la logique d'autorisation dans les contrats intelligents (smart contracts) Solidity représente une vulnérabilité de sécurité critique. Elle découle d'une mauvaise compréhension fondamentale de son comportement par rapport à `msg.sender`. Alors que `tx.origin` identifie de manière cohérente l'EOA (Externally Owned Account / compte détenu extérieurement) qui a initié la transaction, `msg.sender` identifie l'appelant immédiat. Utiliser `tx.origin` pour les vérifications de permissions ne permet pas de valider l'entité interagissant directement avec le contrat.
Cette faille ouvre la porte à des attaques de type hameçonnage (phishing), où un contrat intermédiaire malveillant, invoqué par un utilisateur privilégié, peut appeler avec succès des fonctions protégées sur le contrat cible. La vérification `tx.origin` valide incorrectement l'utilisateur d'origine, permettant au contrat malveillant d'exécuter des actions avec l'autorité de l'utilisateur. Les conséquences vont du vol direct d'Ether et de tokens à la manipulation non autorisée de l'état du contrat, causant potentiellement des pertes financières importantes et des dommages irréparables à l'intégrité et à la réputation du protocole.
Le contrôle d'accès insuffisant est une vulnérabilité critique dans les contrats intelligents Solidity où les restrictions sur qui peut exécuter quelles fonctions sont manquantes ou implémentées de manière incorrecte. Comme Solidity ne dispose pas de modèles de permission intégrés, les développeurs doivent ajouter manuellement des vérifications, souvent en utilisant des modificateurs comme onlyOwner ou en implémentant un contrôle d'accès basé sur les rôles (RBAC). Ne pas le faire correctement crée des failles de sécurité.
Cette vulnérabilité se manifeste généralement par des fonctions non protégées. Des fonctions destinées à des opérations administratives ou sensibles - telles que le transfert de propriété du contrat (changeOwner), le retrait de fonds (withdraw), la mise en pause du contrat, l'émission de tokens ou même la destruction du contrat (selfdestruct) - pourraient être laissées appelables par n'importe quel compte externe. Cela se produit souvent en raison de modificateurs de contrôle d'accès manquants ou d'une visibilité de fonction incorrecte (par exemple, les fonctions sont par défaut public si non spécifié). Les fonctions d'initialisation exposées, qui ne devraient s'exécuter qu'une seule fois, peuvent également être un vecteur d'attaque si elles restent appelables après le déploiement, permettant potentiellement aux attaquants de réinitialiser la propriété ou des paramètres critiques.
Les conséquences sont graves, allant de l'obtention de privilèges administratifs par des utilisateurs non autorisés au vol complet des fonds gérés par le contrat ou à la destruction irréversible du contrat lui-même. Des exploits réels, comme les incidents du portefeuille Parity et le piratage du LAND Token, démontrent le potentiel dévastateur d'un contrôle d'accès insuffisant.
L'atténuation implique l'application rigoureuse de vérifications de contrôle d'accès à toutes les fonctions sensibles, le respect du Principe du moindre privilège, l'utilisation de modèles établis comme Ownable ou RBAC (souvent via des bibliothèques comme celles d'OpenZeppelin) et la réalisation de tests et d'audits approfondis.
Générer de l'aléa sécurisé sur la Machine Virtuelle Ethereum (EVM) est difficile en raison de sa nature déterministe, essentielle pour le consensus du réseau. Cela crée une "illusion d'entropie" où des valeurs apparemment aléatoires dérivées uniquement des données on-chain sont en réalité prévisibles.
Les développeurs utilisent souvent à mauvais escient les variables de bloc facilement disponibles telles que block.timestamp, blockhash, et le prevrandao post-Merge (accessible via block.difficulty) comme sources de pseudo-aléa. Ces variables ne sont pas sécurisées car elles peuvent être prédites ou influencées.
Les mineurs (en Preuve de Travail) ou les validateurs (en Preuve d'Enjeu) peuvent manipuler ces valeurs dans une certaine mesure pour obtenir un avantage injuste, souvent dans le cadre de stratégies MEV (Maximal Extractable Value). Fait crucial, même les utilisateurs réguliers ou les contrats attaquants peuvent souvent prédire les résultats si la logique d'aléa ne repose que sur des entrées connues avant ou pendant l'exécution de la transaction, permettant des exploits comme le front-running. Cette prévisibilité compromet l'équité d'applications telles que les loteries, les jeux et les mints de NFT.
La collision de hachage via `abi.encodePacked` avec plusieurs types dynamiques ne provient pas de faiblesses de la fonction de hachage sous-jacente Keccak-256, mais de la manière dont les données sont encodées avant d'être hachées, spécifiquement lors de l'utilisation de la fonction `abi.encodePacked` de Solidity. Contrairement à la fonction standard `abi.encode`, qui complète les arguments jusqu'à 32 octets (padding) et inclut des préfixes de longueur pour les types dynamiques, `abi.encodePacked` crée un encodage compact et non standard en concaténant les arguments en utilisant le minimum d'octets requis, omettant le padding pour les petits types statiques et, de manière cruciale, omettant les informations de longueur pour les types dynamiques comme `string`, `bytes`, ou les tableaux dynamiques.
Le problème principal survient lorsque `abi.encodePacked` est utilisé avec deux arguments de type dynamique adjacents ou plus. Comme la longueur de chaque argument dynamique n'est pas encodée, la frontière entre eux devient ambiguë dans la chaîne d'octets résultante. Cette ambiguïté permet, souvent de manière triviale, de créer différents ensembles d'entrées logiques qui produisent exactement la même séquence d'octets compactée. Par exemple, `abi.encodePacked("a", "bc")` produit la même sortie en octets que `abi.encodePacked("ab", "c")`.
Lorsque cette sortie d'octets identique est ensuite hachée (par exemple, `keccak256(abi.encodePacked(...))`), cela résulte en la même valeur de hachage, une collision de hachage induite par l'encodage.
Cette vulnérabilité de collision d'encodage peut être exploitée de plusieurs manières si le hachage résultant est utilisé dans un contexte sensible à la sécurité :
Contournement de la vérification de signature : Un attaquant peut prendre une signature valide créée pour un ensemble de paramètres et la réutiliser avec un ensemble de paramètres différent et malveillant qui produit un hachage entrant en collision. La vérification de la signature du contrat (`ecrecover`) réussira, accordant une exécution non autorisée.
Corruption d'état via les collisions de clés de mapping : Si le hachage sujet aux collisions est utilisé comme clé dans un mapping (`mapping(bytes32 => ...)`), un attaquant peut concevoir des entrées pour générer une clé qui entre en collision avec la clé d'un utilisateur légitime, potentiellement écrasant ses données, contournant les contrôles d'accès ou provoquant un déni de service.
Problèmes d'authentification des messages : La vulnérabilité compromet les vérifications qui reposent sur le hachage pour garantir l'intégrité des données, car différents messages logiques peuvent apparaître identiques après le hachage.
Les conséquences d'une exploitation réussie peuvent être graves, incluant l'accès non autorisé à des fonctions ou des données, le vol direct de fonds (Fund Theft), une corruption critique de l'état et un déni de service (DoS).
Les vulnérabilités liées à la perte de précision découlent de la dépendance à l'arithmétique des entiers et de l'absence de prise en charge native des nombres à virgule flottante. Cette conception privilégie l'exécution déterministe mais oblige les développeurs à gérer manuellement les valeurs fractionnaires, créant ainsi des opportunités d'erreurs.
Le problème principal est la troncature de la division entière : Solidity ignore les restes et arrondit les résultats de la division vers zéro. Ce comportement prévisible peut être exploité, souvent via des schémas tels que :
Division avant multiplication : Calculer (a / b) * c au lieu de (a * c) / b tronque le résultat intermédiaire a / b, amplifiant la perte de précision.
Arrondi inférieur à zéro : Si le numérateur A est inférieur au dénominateur B (et que les deux sont positifs), A / B donne toujours 0. C'est risqué pour les calculs impliquant de petits frais, récompenses ou conversions de jetons.
Les attaquants exploitent ces propriétés mathématiques pour manipuler la logique du contrat à des fins de gain financier. Les stratégies courantes incluent :
Manipulation de l'état/du prix : Déclencher des erreurs d'arrondi pour fausser les valeurs critiques du protocole telles que les taux de change, les réserves de pool, les prix des parts de coffre-fort ou les ratios de garantie, qui peuvent ensuite être exploités dans des transactions ultérieures.
Ciblage des cas limites : Utiliser des transactions avec de très petites entrées ou des entrées conçues pour interagir avec de grandes valeurs internes afin de maximiser l'impact de la troncature, entraînant souvent des calculs aboutissant à zéro.
Les attaques réussies par perte de précision peuvent entraîner des conséquences négatives significatives :
Coûts réduits : Les attaquants paient des frais/coûts inférieurs ou nuls.
Gains gonflés : Les attaquants reçoivent illégitimement plus de jetons, de parts ou de récompenses.
Opportunités d'arbitrage : Création de différences de prix artificielles au sein du protocole que les attaquants peuvent exploiter.
Contournement des mécanismes de risque : Contourner les liquidations ou autres contrôles de sécurité en raison de calculs inexacts.
Épuisement progressif des fonds : Siphonner de la valeur par des transactions répétées exploitant de minuscules erreurs d'arrondi ("attaques à 1 wei").
L'atténuation implique une gestion arithmétique prudente, comme effectuer les multiplications avant les divisions, utiliser une mise à l'échelle numérique (simulant les mathématiques à virgule fixe), employer des bibliothèques mathématiques spécialisées et implémenter une logique d'arrondi appropriée. Les vérifications de dépassement standard (comme SafeMath ou Solidity >=0.8) n'empêchent pas la perte de précision due à la division.
Cette vulnérabilité se produit lorsqu'un contrat intelligent utilise incorrectement msg.value au sein d'une boucle. Le problème principal vient du fait que msg.value reste constant pendant tout le contexte d'exécution d'une transaction. Si une boucle itère plusieurs fois, effectuant des vérifications ou des actions basées sur cette valeur initiale de msg.value à chaque itération sans suivre correctement la valeur cumulative traitée ou dépensée au fil de ces itérations, cela crée une opportunité d'exploitation.
Un attaquant peut exploiter cela en envoyant un montant spécifique d'Ether pour déclencher la fonction vulnérable. À l'intérieur de la boucle, des vérifications comme require(msg.value >= amount_per_item) peuvent réussir de manière répétée, ou des mises à jour d'état peuvent utiliser incorrectement la pleine valeur initiale de msg.value plusieurs fois. Cela se produit parce que la logique du contrat ne tient pas compte de la valeur effectivement 'dépensée' ou allouée lors des itérations précédentes de la même boucle.
Cette faille permet à un attaquant de déclencher des actions (comme des transferts d'Ether ou des crédits de solde internes) dont la valeur totale dépasse considérablement l'Ether qu'il a réellement envoyé avec la transaction.
ecrecover est un précompilé EVM essentiel permettant aux contrats intelligents de récupérer l'adresse du signataire à partir d'un hachage de message et d'une signature ECDSA (v, r, s). Ceci active des fonctionnalités cruciales comme la vérification de messages signés hors chaîne pour les méta-transactions ou les fonctions `permit`. Cependant, son utilisation directe présente des risques de sécurité importants, souvent sous-estimés, si elle n'est pas gérée avec soin.
Vulnérabilité du retour de l'adresse zéro : Un problème critique découle de la gestion unique des erreurs par `ecrecover`. Lorsqu'il est confronté à une signature invalide ou mathématiquement impossible, il n'annule pas (revert) la transaction. Au lieu de cela, il échoue silencieusement et retourne l'adresse zéro (`address(0)`). Les contrats qui appellent `ecrecover` mais ne disposent pas d'une vérification de l'adresse zéro sont très vulnérables. Un attaquant peut intentionnellement soumettre des données de signature invalides, amenant `ecrecover` à retourner `address(0)`. Si ce résultat n'est pas explicitement vérifié et rejeté, le contrat pourrait continuer incorrectement, en traitant `address(0)` comme le signataire légitime. Cela peut entraîner des conséquences graves, comme des changements d'état non autorisés, des émissions d'événements incorrectes ou l'octroi de permissions, surtout si l'adresse zéro possède des privilèges spéciaux ou un état significatif dans la logique spécifique du contrat. Un code robuste doit toujours valider `recoveredAddress != address(0)` immédiatement après l'appel à `ecrecover`.
Vulnérabilité de la malléabilité de la signature : Le deuxième risque majeur provient d'une propriété inhérente à l'algorithme ECDSA lui-même : la malléabilité de la signature. Pour un message et une clé privée donnés, plusieurs représentations de signature distinctes mais cryptographiquement valides peuvent exister (spécifiquement, une signature utilisant le composant `s` peut souvent être transformée en une signature valide utilisant `n-s`, où `n` est l'ordre de la courbe). Cela devient une vulnérabilité si les contrats supposent à tort qu'une signature pour un message est unique. Les attaquants peuvent exploiter cela en contournant les vérifications d'unicité. Par exemple, si un contrat utilise le hachage de la signature elle-même comme nonce pour empêcher les rejeux (un modèle défectueux), un attaquant peut prendre une signature valide, calculer son équivalent malléable et la soumettre pour exécuter à nouveau l'action, car les hachages des signatures différeront. Cela peut également provoquer un comportement inattendu ou un rejeu dans les systèmes attendant une forme de signature spécifique, si des systèmes externes ou des parties de la logique du contrat n'ont pas été conçus pour gérer les deux formes de signature valides. Une atténuation efficace implique d'imposer la canonisation de la signature – une vérification implémentée de manière robuste dans les bibliothèques standard comme ECDSA d'OpenZeppelin, qui devrait être préférée à l'utilisation directe de `ecrecover`.
Attaque par rejeu inter-chaînes (CCRA) : Une transaction validement exécutée sur une chaîne EVM est capturée et soumise à nouveau avec succès sur une autre chaîne EVM. Cela exploite les similitudes dans les formats de transaction et les signatures entre les chaînes, en particulier lorsque les transactions ne possèdent pas d'identifiants de chaîne uniques (Chain ID). Le hard fork Ethereum/Ethereum Classic est un exemple classique où ce risque s'est matérialisé. L'EIP-155 a été introduit pour atténuer ce risque en intégrant l'identifiant de chaîne (Chain ID) dans les signatures de transaction standard, les rendant ainsi spécifiques à chaque chaîne. Cependant, les smart contracts utilisant une vérification de signature personnalisée doivent également vérifier explicitement le Chain ID. L'exploit Optimism de 20 millions de dollars contre Wintermute résultait d'une telle absence de vérification du Chain ID dans un contrat déployé sur plusieurs chaînes (cross-chain).
Rejeu au niveau du Smart Contract (Même chaîne) : Un message signé ou une transaction est rejoué(e) contre le même smart contract, ou potentiellement contre un autre contrat sur la même chaîne. Cela exploite généralement des vulnérabilités dans la logique propre du contrat, en particulier dans les schémas de vérification de signature personnalisés utilisés pour des fonctionnalités telles que les méta-transactions ou les fonctions `permit` d'ERC-20. La faille la plus courante est l'absence ou la mauvaise implémentation d'un nonce au niveau applicatif. Un nonce (« nombre utilisé une seule fois ») est un compteur unique associé au signataire qui doit être inclus dans le condensé (digest) du message signé et suivi on-chain par le contrat pour garantir que chaque signature spécifique n'autorise qu'une seule action.
La prise en charge par Solidity de l'héritage multiple permet à un contrat d'hériter simultanément des fonctionnalités de plusieurs contrats parents. Bien que puissant pour la réutilisation du code, cela introduit une ambiguïté potentielle, connue sous le nom de « problème du diamant » : si deux contrats de base ou plus définissent une fonction avec le même nom et les mêmes paramètres.
Pour résoudre ce problème, Solidity utilise l'algorithme de linéarisation C3 pour établir un ordre de résolution de méthode (Method Resolution Order, MRO) unique et déterministe pour chaque contrat. Ce MRO dicte la séquence précise dans laquelle les contrats de base sont vérifiés lors de la résolution des appels de fonction.
La vulnérabilité découle directement de la manière dont ce MRO est déterminé. Le facteur crucial est l'ordre dans lequel le développeur liste les contrats de base dans l'instruction `is`. Solidity exige de lister les contrats du « plus basique » au « plus dérivé ». Cet ordre spécifié influence directement le MRO final généré par l'algorithme C3.
La vulnérabilité se produit lorsque le développeur fournit un ordre d'héritage qui ne correspond pas à la hiérarchie logique ou à la priorité prévue. Si un contrat plus général est listé après un contrat plus spécifique, ou si l'ordre provoque autrement la priorisation par le MRO d'une implémentation de fonction non intentionnelle, le contrat peut se comporter de manière inattendue. Par exemple, un appel pourrait être résolu vers une fonction de base dépourvue de contrôles de sécurité critiques ou de logique mise à jour qui a été implémentée dans une surcharge de contrat dérivé prévue mais incorrectement ordonnée.
Les conséquences de l'exécution de la mauvaise fonction en raison d'un ordre d'héritage incorrect incluent le contournement des contrôles d'accès, l'exécution d'une logique métier obsolète ou incorrecte, la corruption de l'état et des pertes financières potentielles. Essentiellement, le flux d'exécution réel du contrat s'écarte de la conception du développeur, compromettant la sécurité et la fonctionnalité.
La Valeur Maximale Extractible (Maximal Extractable Value - MEV) représente le profit que les producteurs de blocs et les chercheurs spécialisés (searchers) peuvent capturer en manipulant l'inclusion et l'ordre des transactions au sein d'un bloc, au-delà des récompenses de bloc standard et des frais de gaz. Initialement appelée "Valeur Extractible par les Mineurs" (Miner Extractable Value), le nom a évolué vers "Maximale" pour refléter la cupidité de la récompense.
La MEV survient parce que les transactions en attente se trouvent souvent dans une zone d'attente publique appelée mempool, visible par tous. Les producteurs de blocs ont le pouvoir de décider de l'ordre final des transactions dans un bloc. Des robots automatisés, gérés par des "chercheurs" (searchers), surveillent constamment le mempool, simulent les résultats potentiels et exploitent les opportunités rentables en ordonnant stratégiquement les transactions, utilisant souvent des enchères sur le gaz (Priority Gas Auctions - PGA) pour garantir un placement préférentiel.
Les stratégies MEV courantes, souvent considérées comme des attaques, comprennent :
Le Front-Running : Placer la transaction d'un attaquant avant celle d'une victime (par exemple, une grosse transaction sur un DEX) pour profiter de l'impact anticipé sur le prix.
Les Attaques Sandwich (Sandwich Attacks) : Une combinaison de front-running et de back-running (suivi) de la transaction DEX d'une victime pour capturer la différence de prix (glissement ou slippage) causée par la manipulation.
La Liquidité Juste-à-Temps (JIT Liquidity) : Ajouter et retirer temporairement de la liquidité autour d'un gros échange (swap) sur des DEX à liquidité concentrée pour capturer les frais.
La Manipulation d'Oracle (Oracle Manipulation) : Exploiter les mises à jour ou les inexactitudes des oracles de prix à des fins lucratives, ce qui a souvent un impact sur les protocoles de prêt.
D'autres types incluent le sniping de NFT (NFT sniping) et les attaques par poussière (dust attacks).
Les conséquences pour les utilisateurs incluent une augmentation des coûts de transaction due aux guerres du gaz (gas wars), de moins bons prix d'exécution (glissement ou slippage) sur les transactions, une perte impermanente (impermanent loss) exacerbée pour les fournisseurs de liquidité et des liquidations déclenchées injustement.
Les stratégies d'atténuation visent à réduire l'impact négatif de la MEV. Envoyer des transactions via des mempools privés ou des relais (comme Flashbots Protect ou MEV Blocker) les cache de la vue publique. Les conceptions au niveau applicatif comme les schémas Commit-Reveal (engagement-révélation) masquent les détails de la transaction jusqu'à ce que l'ordre soit fixé, tandis que l'utilisation de prix moyens pondérés dans le temps (Time-Weighted Average Prices - TWAP) pour les oracles peut réduire les risques de manipulation.