Les attaques de réentrance restent l’une des menaces les plus notoires et persistantes pour la sécurité des smart contracts sur Ethereum et les chaînes compatibles EVM. Malgré des années d’efforts dans l’industrie, de nouveaux protocoles continuent de présenter des vulnérabilités de réentrance, entraînant des pertes financières significatives—parfois atteignant des millions de dollars. Ces exploits apparaissent souvent dans les contrats DeFi de prêt, de staking et de ponts, où les appels externes et les transferts de tokens sont courants.
Dans cet article, nous disséquons la mécanique d’une attaque de réentrance, identifions les modèles typiques de contrats vulnérables, et partageons des stratégies de mitigation éprouvées. À partir de notre analyse de plus de 282 audits de smart contracts chez Soken, nous mettons en lumière comment des pratiques de codage Solidity nuancées et des workflows de test complets peuvent contrecarrer les menaces de réentrance. Ce document relie également ces risques à des problématiques connexes telles que l’usage abusif de delegatecall et les tactiques de flash loan pour fournir une compréhension globale essentielle à tout développeur Web3 ou auditeur de sécurité sérieux.
Qu’est-ce qu’une attaque de réentrance et comment exploite-t-elle les smart contracts ?
Une attaque de réentrance se produit lorsqu’un contrat malveillant appelle de manière répétée une fonction d’un contrat vulnérable avant que l’exécution initiale ne soit terminée, exploitant ainsi l’état incohérent du contrat.
En pratique, une vulnérabilité de réentrance découle de changements d’état mal ordonnés et d’appels externes à l’intérieur d’un contrat. Lorsque le contrat transfère de l’ETH ou des tokens via un call ou un transfer vers une adresse non fiable, le destinataire peut ré-entrer de manière récursive dans la fonction vulnérable avant que les variables d’état ne soient mises à jour. Cela permet à l’attaquant de vider les fonds ou de manipuler la logique du contrat de façon inattendue.
Par exemple, le célèbre hack du DAO en 2016 a abouti à l’exploitation d’environ 60 millions de dollars via des failles de réentrance — une leçon à méditer. Depuis, l’industrie a identifié que la cause racine est la mise à jour du solde ou de l’état du contrat qui n’intervient qu’après les appels externes, créant ainsi une fenêtre d’état incohérent exploitée par l’attaquant.
Insight d’experts issus des audits Soken : « Dans plus de 40 % de nos récents audits de sécurité DeFi, des schémas de réentrance apparaissent sous diverses formes, souvent des variations subtiles impliquant des contrats proxy ou des appels en cascade. Les identifier nécessite une analyse approfondie des appels inter-contrats au-delà d’une inspection contractuelle unique. »
Modèles Solidity courants qui causent des vulnérabilités de réentrance
Les vulnérabilités de réentrance résultent typiquement d’une séquence spécifique d’opérations : des appels externes (comme les transferts ETH ou tokens) qui interviennent avant que le contrat ne mette à jour ses variables d’état internes.
Le schéma le plus vulnérable ressemble à ceci :
function withdraw(uint256 amount) public {
require(balances[msg.sender] >= amount);
(bool success, ) = msg.sender.call{value: amount}("");
require(success);
balances[msg.sender] -= amount;
}
Ici, l’appel externe utilisant call.value() se produit avant la déduction du solde de l’utilisateur. Lors de cet appel externe, la fonction de fallback d’un attaquant peut invoquer récursivement withdraw(), vidant ainsi les fonds avant la mise à jour des soldes.
Parmi les constructions risquées clés, on trouve :
- Appels externes avant mises à jour d’état : Envoi d’ETH ou de tokens avant de mettre à jour les soldes.
- Utilisation de
callousendsans précautions : L’appel externe via des fonctions de bas niveau contourne les contrôles et peut invoquer des fonctions fallback. - Absence de garde anti-réentrance : Pas de mutex ou autres mécanismes de verrouillage.
- Chaînes d’appels complexes avec delegatecall : Ces dernières peuvent indirectement ouvrir la porte à la réentrance via des appels externes dans les contrats appelés.
À l’inverse, le schéma plus sûr inverse l’ordre :
function withdraw(uint256 amount) public {
require(balances[msg.sender] >= amount);
balances[msg.sender] -= amount; // mise à jour d’état en premier
(bool success, ) = msg.sender.call{value: amount}("");
require(success);
}
Comment prévenir les attaques de réentrance en Solidity : bonnes pratiques et outils
La défense la plus efficace contre la réentrance est de mettre à jour l’état avant les appels externes combinée à des motifs explicites de protection contre la réentrance.
Techniques courantes de mitigation :
-
Pattern Checks-Effects-Interactions :
Toujours mettre à jour l’état interne avant d’effectuer des appels externes. Cela minimise l’état incohérent du contrat durant les appels externes. -
Gardes anti-réentrance / Mutex :
LeReentrancyGuardde Solidity issu d’OpenZeppelin utilise un mutex simple pour bloquer les appels imbriqués. On l’intègre ainsi :
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
contract SecureContract is ReentrancyGuard {
mapping(address => uint256) balances;
function withdraw(uint256 amount) public nonReentrant {
require(balances[msg.sender] >= amount);
balances[msg.sender] -= amount;
(bool success, ) = msg.sender.call{value: amount}("");
require(success);
}
}
-
Limiter les appels externes :
Réduire au minimum les transferts ou interactions dans les fonctions critiques — privilégier les paiements en mode pull où les utilisateurs retirent explicitement leurs fonds. -
Analyse statique et outils automatisés :
Des outils comme Slither et les frameworks d’audit de Soken détectent les vulnérabilités de réentrance. -
Utiliser les dernières versions de Solidity et fonctionnalités :
Solidity 0.8+ intègre des vérifications automatiques d’overflow, mais les risques de réentrance persistent, d’où la nécessité de combiner sécurité du code et patterns robustes.
Méthodologie Soken : Nos audits intègrent modélisation manuelle des menaces, exécution symbolique et fuzzing, permettant de détecter des flux de réentrance complexes souvent manqués par les scanners génériques.
Exemples réels d’exploits de réentrance et leur impact
Les attaques de réentrance restent courantes malgré une sensibilisation accrue. Voici un tableau comparatif de hacks notables illustrant la diversité et le coût de ces exploits :
| Incident | Date | Montant de la perte | Schéma exploité | Leçon clé |
|---|---|---|---|---|
| Le hack du DAO | 2016-06 | ~60 millions $ | Réentrance dans la proposition splitDAO | Mettre à jour l’état avant appel externe |
| Le hack de Lendf.Me | 2020-04 | 25 millions $+ | Réentrance amplifiée par flash loan | Combiner garde anti-réentrance + résilience flash loan |
| Hack Euler Finance | 2023-03 | 197 millions $ | Réentrance imbriquée + delegatecall | Les chaînes d’appels complexes augmentent le risque |
| Hack récent de pont DeFi | 2025-11 | 15 millions $ | Exploit de réentrance via transfert de tokens | Auditer les interactions inter-contrats |
Remarque : Les audits Soken insistent sur une sécurité multi-couches, notamment autour des appels inter-contrats et des standards token qui peuvent déclencher dynamiquement des fonctions fallback.
Delegatecall et vecteurs de flash loan : amplifier les risques de réentrance
Delegatecall, qui exécute du code dans le contexte du contrat appelant, peut obscurcir les risques de réentrance et élargir la surface d’attaque, surtout combiné aux flash loans.
Les attaques de réentrance exploitent souvent les flash loans pour maximiser le capital destiné au vidage récursif :
- Les flash loans fournissent une grande liquidité immédiate en une seule transaction.
- Un attaquant utilise ces fonds flash-loanés pour déclencher des appels réentrants afin de vider la liquidité du protocole avant de rembourser le prêt.
- L’usage de delegatecall peut involontairement activer du code externe non fiable modifiant l’état du contrat ou permettant des réentrées.
Insight sécurité : « Dans l’expérience Soken, la mitigation de la réentrance dépasse les contrats uniques pour englober la conception du protocole — en particulier quand delegatecall et flash loans cohabitent. Les contrats doivent combiner garde anti-réentrance avec validation d’entrée et contrôles spécifiques aux flash loans (plafonds de montant, limites sur les appels récurrents). »
Tableau comparatif : techniques courantes pour prévenir la réentrance
| Technique | Description | Avantages | Inconvénients | Usage |
|---|---|---|---|---|
| Checks-Effects-Interactions | Mettre à jour état avant appels externes | Simple, efficace | Nécessite de la rigueur | Pratique de sécurité de base |
| Garde anti-réentrance (mutex) | Modifier Solidity pour éviter la réentrée | Bloque explicitement les appels récursifs | Ajoute un léger coût en gas | Recommandé pour toutes les fonctions de retrait |
| Pattern Pull Payment | Laisser les utilisateurs retirer volontairement | Minimise le risque de transfert automatique | Nécessite une interaction utilisateur | Idéal pour contrats d’entiercement et staking |
| Analyse statique et dynamique | Audits automatisés et manuels | Détecte les vulnérabilités tôt | Peut manquer des flux cross-contract complexes | Essentiel pour la garantie avant déploiement |
| Vérification Flash Loan & Delegatecall | Valider l’appelant et le contexte transactionnel | Empêche la réentrance amplifiée par prêt flash | Complexe à implémenter | Critique dans les protocoles DeFi utilisant des prêts |
Exemple de code Solidity : un contrat vulnérable à la réentrance et sa correction
Voici un contrat minimalement vulnérable et sa version renforcée avec garde anti-réentrance :
// Vulnérable : retrait avant la mise à jour du solde
contract Vulnerable {
mapping(address => uint256) public balances;
function deposit() external payable {
balances[msg.sender] += msg.value;
}
function withdraw(uint256 amount) external {
require(balances[msg.sender] >= amount, "Insufficient funds");
(bool success, ) = msg.sender.call{value: amount}("");
require(success, "Transfer failed");
balances[msg.sender] -= amount; // Vulnérable : état mis à jour après l’appel externe
}
}
Corrigé avec ReentrancyGuard d’OpenZeppelin et effets avant interaction :
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
contract Secure is ReentrancyGuard {
mapping(address => uint256) public balances;
function deposit() external payable {
balances[msg.sender] += msg.value;
}
function withdraw(uint256 amount) external nonReentrant {
require(balances[msg.sender] >= amount, "Insufficient funds");
balances[msg.sender] -= amount; // État mis à jour avant l’appel externe
(bool success, ) = msg.sender.call{value: amount}("");
require(success, "Transfer failed");
}
}
Astuce pro : Combinez toujours le pattern checks-effects-interactions avec des gardes anti-réentrance. Auditer les graphes d’appels contractuels et tester avec des outils de fuzzing permet de révéler des vulnérabilités de réentrance multi-sauts subtiles avant le déploiement.
Les vulnérabilités de réentrance restent une menace critique pour les smart contracts, en particulier dans les écosystèmes DeFi complexes utilisant flash loans et delegatecall. Les défenses les plus efficaces allient discipline de codage (checks-effects-interactions), gardes explicites, audits rigoureux et validation transactionnelle contextuelle.
Chez Soken, notre recherche en sécurité intègre des techniques manuelles et automatisées affinées à partir de plus de 282 audits de smart contracts, identifiant et remédiant systématiquement la réentrance et les risques associés. S’appuyer sur les meilleures pratiques et des méthodologies de test approfondies est impératif pour tout projet manipulant des fonds utilisateurs.
Besoin d’un accompagnement expert en sécurité ? L’équipe d’auditeurs Soken a passé en revue plus de 255 smart contracts et sécurisé plus de 2 milliards de dollars de valeur protocolaire. Que vous ayez besoin d’un audit complet, d’une évaluation gratuite X-Ray de sécurité, ou d’aide pour naviguer dans les régulations crypto, nous sommes prêts à vous assister.