Les attaques par réentrance représentent l'une des menaces de sécurité les plus importantes dans le développement de contrats intelligents. Cette analyse technique explique les mécanismes derrière les vulnérabilités de réentrance et fournit des stratégies de défense complètes pour protéger vos contrats.
Qu'est-ce qu'une attaque par réentrance ?
La réentrance se produit lorsqu'une fonction dans un contrat intelligent peut être interrompue pendant son exécution et appelée à nouveau avant que la première invocation ne soit terminée. En termes techniques, la réentrance exploite le contexte d'exécution d'un contrat intelligent en manipulant le flux de contrôle par le biais d'appels externes.
Lorsque le Contrat A interagit avec le Contrat B, la vulnérabilité apparaît parce que le Contrat B peut rappeler le Contrat A pendant que l'exécution du Contrat A est toujours en cours. Ce modèle d'appel récursif peut être exploité pour manipuler l'état du contrat et vider des fonds.
Mécanismes d'attaque : Comment fonctionne la réentrance
Considérez un scénario avec deux contrats :
Contrat A : Un contrat vulnérable détenant 10 ETH
Contrat B : Un contrat malveillant avec 1 ETH déposé dans le Contrat A
Le flux d'attaque suit ce modèle :
Le contrat B appelle la fonction withdraw() dans le contrat A
Le contrat A vérifie que le solde du contrat B est supérieur à 0 (passe le contrôle)
Le contrat A envoie 1 ETH au contrat B avant de mettre à jour son enregistrement de solde.
Le transfert ETH déclenche la fonction fallback du Contrat B
À l'intérieur de la fonction de secours, le Contrat B appelle à nouveau la fonction withdraw() du Contrat A.
Puisque le contrat A n'a pas encore mis à jour le solde du contrat B, la vérification passe à nouveau.
Le contrat A envoie un autre ETH au contrat B
Ce cycle se répète jusqu'à ce que le Contrat A soit vidé de ses fonds.
La vulnérabilité critique réside dans l'ordre d'exécution du Contrat A : il effectue l'appel externe ( envoyant ETH) avant de mettre à jour son état interne ( en réglant le solde à zéro).
Trois techniques de défense contre la réentrance
1. Protection au niveau de la fonction avec le modificateur noReentrant
Le modificateur noReentrant implémente un mécanisme de verrouillage qui empêche une fonction d'être appelée de manière récursive :
solidité
// Variable d'état pour suivre la réentrance
bool privé verrouillé = faux;
// Modificateur pour prévenir la réentrance
modificateur noReentrant() {
require(!locked, "Appel réentrant");
verrouillé = vrai;
_;
verrouillé = faux;
}
// Fonction protégée
fonction retirer() public noReentrant {
// Logique de la fonction ici
}
Cette approche bloque les tentatives de réentrance en maintenant une variable d'état à l'échelle du contrat qui empêche l'exécution concurrente des fonctions protégées.
2. Modèle d'interaction Check-Effect
Ce modèle restructure le code pour suivre une séquence d'opérations spécifique :
Vérifiez : Vérifiez les conditions ( par exemple, solde > 0)
Effet : Mettre à jour les variables d'état (e.g., définir le solde = 0)
Interaction : Effectuer des appels externes ( par exemple, transférer ETH)
Comparer le code vulnérable au code protégé :
Vulnérable :
solidité
function withdraw() externe {
uint bal = balances[msg.sender];
require(bal > 0);
// Interaction avant l'effet (vulnérable)
(bool sent, ) = msg.sender.call{value: bal}("");
require(sent, "Échec de l'envoi d'Ether");
balances[msg.sender] = 0; // Peut ne jamais être atteint si une réentrance se produit
}
Protégé :
solidité
function withdraw() externe {
uint bal = balances[msg.sender];
require(bal > 0);
// Effet avant l'interaction (secure)
balances[msg.sender] = 0;
(bool sent, ) = msg.sender.call{value: bal}("");
require(envoyé, "Échec de l'envoi d'Ether");
}
En mettant à jour l'état avant les interactions externes, le contrat reste sécurisé même si l'appel externe déclenche un appel de fonction réentrante.
3. Protection Inter-Contrats avec GlobalReentrancyGuard
Pour les projets avec plusieurs contrats interagissant, un garde de réentrance partagé fournit une protection complète :
solidity
// Contrat central pour la protection contre la réentrance
contrat GlobalReentrancyGuard {
mapping(adresse => bool) privé _status;
// Utilisation du garde dans un contrat
contrat ProtectedContract est GlobalReentrancyGuard {
fonction protectedFunction() externe {
_beforeNonReentrant();
// Logique de fonction protégée ici
_afterNonReentrant();
}
}
Cette technique empêche la réentrance croisée des contrats en maintenant un registre d'état global qui suit l'état d'exécution à travers plusieurs contrats dans votre écosystème.
Meilleures pratiques de sécurité
Pour assurer une protection complète contre les attaques de réentrance :
Appliquer plusieurs couches de défense : Combiner le modèle de vérification-effet-interaction avec des gardes de réentrance pour une sécurité maximale
Effectuez des tests approfondis : Utilisez des outils spécialisés comme Mythril pour détecter d'éventuelles vulnérabilités de réentrance
Suivez les modèles établis : Mettez en œuvre des mesures de sécurité de manière cohérente dans tous les contrats de votre projet
Utilisez des dépendances auditées : Intégrez des bibliothèques éprouvées avec des dossiers de sécurité prouvés
Mettez d'abord à jour les soldes : Modifiez toujours l'état avant d'effectuer des appels externes ou des transferts.
En mettant en œuvre ces mécanismes de défense, vous pouvez protéger efficacement vos contrats intelligents contre l'un des vecteurs d'attaque les plus dangereux en matière de sécurité blockchain.
Cette page peut inclure du contenu de tiers fourni à des fins d'information uniquement. Gate ne garantit ni l'exactitude ni la validité de ces contenus, n’endosse pas les opinions exprimées, et ne fournit aucun conseil financier ou professionnel à travers ces informations. Voir la section Avertissement pour plus de détails.
Sécurité des Smart Contracts : Comprendre et prévenir les vulnérabilités de réentrance
Les attaques par réentrance représentent l'une des menaces de sécurité les plus importantes dans le développement de contrats intelligents. Cette analyse technique explique les mécanismes derrière les vulnérabilités de réentrance et fournit des stratégies de défense complètes pour protéger vos contrats.
Qu'est-ce qu'une attaque par réentrance ?
La réentrance se produit lorsqu'une fonction dans un contrat intelligent peut être interrompue pendant son exécution et appelée à nouveau avant que la première invocation ne soit terminée. En termes techniques, la réentrance exploite le contexte d'exécution d'un contrat intelligent en manipulant le flux de contrôle par le biais d'appels externes.
Lorsque le Contrat A interagit avec le Contrat B, la vulnérabilité apparaît parce que le Contrat B peut rappeler le Contrat A pendant que l'exécution du Contrat A est toujours en cours. Ce modèle d'appel récursif peut être exploité pour manipuler l'état du contrat et vider des fonds.
Mécanismes d'attaque : Comment fonctionne la réentrance
Considérez un scénario avec deux contrats :
Le flux d'attaque suit ce modèle :
La vulnérabilité critique réside dans l'ordre d'exécution du Contrat A : il effectue l'appel externe ( envoyant ETH) avant de mettre à jour son état interne ( en réglant le solde à zéro).
Trois techniques de défense contre la réentrance
1. Protection au niveau de la fonction avec le modificateur noReentrant
Le modificateur noReentrant implémente un mécanisme de verrouillage qui empêche une fonction d'être appelée de manière récursive :
solidité // Variable d'état pour suivre la réentrance bool privé verrouillé = faux;
// Modificateur pour prévenir la réentrance modificateur noReentrant() { require(!locked, "Appel réentrant"); verrouillé = vrai; _; verrouillé = faux; }
// Fonction protégée fonction retirer() public noReentrant { // Logique de la fonction ici }
Cette approche bloque les tentatives de réentrance en maintenant une variable d'état à l'échelle du contrat qui empêche l'exécution concurrente des fonctions protégées.
2. Modèle d'interaction Check-Effect
Ce modèle restructure le code pour suivre une séquence d'opérations spécifique :
Comparer le code vulnérable au code protégé :
Vulnérable : solidité function withdraw() externe { uint bal = balances[msg.sender]; require(bal > 0);
}
Protégé : solidité function withdraw() externe { uint bal = balances[msg.sender]; require(bal > 0);
}
En mettant à jour l'état avant les interactions externes, le contrat reste sécurisé même si l'appel externe déclenche un appel de fonction réentrante.
3. Protection Inter-Contrats avec GlobalReentrancyGuard
Pour les projets avec plusieurs contrats interagissant, un garde de réentrance partagé fournit une protection complète :
solidity // Contrat central pour la protection contre la réentrance contrat GlobalReentrancyGuard { mapping(adresse => bool) privé _status;
}
// Utilisation du garde dans un contrat contrat ProtectedContract est GlobalReentrancyGuard { fonction protectedFunction() externe { _beforeNonReentrant();
}
Cette technique empêche la réentrance croisée des contrats en maintenant un registre d'état global qui suit l'état d'exécution à travers plusieurs contrats dans votre écosystème.
Meilleures pratiques de sécurité
Pour assurer une protection complète contre les attaques de réentrance :
En mettant en œuvre ces mécanismes de défense, vous pouvez protéger efficacement vos contrats intelligents contre l'un des vecteurs d'attaque les plus dangereux en matière de sécurité blockchain.