Réentrance inter-chaînes
Plusieurs contrats avec différentes instances doivent être pris en compte simultanément dans la sécurité inter-chaînes, ce paradigme introduit de nouveaux défis conceptuels. Ce que nous pourrions considérer comme sécurisé dans une seule chaîne peut être une faille de sécurité critique dans un protocole inter-chaînes. Quelques exemples classiques sont les signatures et les attaques par rejeu.
Dans cet article, nous allons passer en revue un vecteur d'attaque trouvé dans l'un de nos audits qui peut être exploité dans un contexte de transmission de messages inter-chaînes. L'attaque a été trouvée par examen manuel et elle a été testée.
Contenu:
- Construire un contrat NFT inter-chaînes
- Pont abstrait
- Bibliothèques sécurisées
- Exploitation d'un appel externe non sécurisé
- Exemple de contrat d'attaquant dans Solidity
Construire un contrat NFT inter-chaînes
Comment réaliser une collecte NFT inter-chaînes et quelles précautions faut-il prendre en compte ?
Disons que nous voulons créer une collection NFT qui peut être utilisée dans 2 chaînes différentes, appelons-les chainA et chainB.
Nous pouvons le faire de manière très simple en déployant le contrat NFT dans les deux chaînes et en maintenant une seule instance en utilisant un pont pour effectuer des appels inter-chaînes. Cela garantit qu'il n'y a qu'un seul propriétaire pour un identifiant donné et empêche la création de plusieurs copies du même identifiant, ce qui entraînerait un jeton fongible. Nous avons également besoin d'une fonction de transfert de jetons entre les chaînes prises en charge.
Dans cet exemple, le nom de la collection NFT est crossChainWarriors. Si un utilisateur possède un guerrier dans la chaîne A et souhaite le transférer dans la chaîne B, un moyen simple de le faire est de le brûler dans la chaîne A et de le frapper dans la chaîne B en utilisant la magie et la sécurité du pont.
Pont abstrait
Un petit schéma nous aidera à comprendre où nous en sommes.
Comme nous le voyons dans le diagramme, l'exécution d'un transfert inter-chaînes dans chainA déclenche un événement qui est écouté par les validateurs de pont. Ils appellent ensuite le contrat de destination avec les données récupérées.
Bibliothèques sécurisées
Voyons la fonction de menthe crossChainWarriors :
C'est tout à fait normal, mais explorons ce que fait _safeMint :
La fonction a la même logique que _mint mais elle effectue une vérification supplémentaire pour s'assurer que le destinataire peut gérer la fonctionnalité ERC721. Ceci est important lorsque le destinataire n'est pas un EOA.
Mais que fait _checkOnERC721Received ?
C'est un gros morceau de code mais gardons seulement notre attention sur cette partie :
Il est important de noter qu'un appel externe au destinataire est en cours d'exécution. Si vous pensez aux attaques de réentrée, les appels externes sont des portes dérobées pour les pirates.
Cet appel externe permet de demander au destinataire s'il peut gérer ERC721, plus précisément s'il implémente l'interface récepteur IERC721. Le destinataire doit renvoyer IERC721Receiver.onERC721Received.selector pour réussir la vérification, mais avant cela, le contrat peut également effectuer d'autres transactions.
Le contrat le plus simple connaissant l'interface du récepteur ERC721 serait :
Comme on le voit dans le code ci-dessous, avant de retourner le sélecteur, vous pouvez faire tout ce que vous voulez, mais n'oubliez pas de retourner le sélecteur à la fin.
Mais, ce contrat gère-t-il les transferts ERC721 ?
La réponse est tout simplement non, les NFT envoyés à ce contrat seront perdus à jamais.
En termes simples, l'idée derrière _safeMint est de fournir une vérification supplémentaire pour empêcher les utilisateurs de perdre leurs jetons. Mais cette vérification vérifie uniquement que le récepteur connaît IERC721Receiver et ne garantit pas que le destinataire peut gérer ERC721. De plus, cela s'accompagne du coût supplémentaire d'un appel externe non sécurisé.
Il est maintenant temps d'examiner comment l'appel externe dangereux pourrait être utilisé pour compromettre le contrat
Exploitation d'un appel externe non sécurisé
Revenons à la fonction originale de menthe en nous concentrant uniquement sur la 3ème ligne.
Considérez maintenant ceci :
l'exécution peut être interrompue à la ligne 3, ce qui permet d'exécuter un appel externe avant de mettre à jour les variables d'état. Portez une attention particulière aux variables d'état modifiées dans cette fonction et à l'endroit où elles sont modifiées.
tokenIds.increment()
Une première approche naturelle serait d'appeler à nouveau mint()
mais la fonction interne ERC721 _mint échouera car l'identifiant du jeton n'a pas été incrémenté et existe déjà.
require(!_exists(tokenId), "ERC721: token already minted");
Quelles autres fonctions peuvent être appelées depuis le contrat de l'attaquant ?
- crossChainTransfer() :
Un chemin d'attaque possible pourrait être :
- Appelez mint() dans chainA avec un contrat d'attaquant comme destinataire
- Entrez à nouveau le contrat et appelez crossChainTransfer pour transférer le nouvel identifiant vers chainB.
- appelez à nouveau mint() sur chainA
Nous avons maintenant atteint la dernière étape, qui consiste à rédiger un contrat d'attaquant et à tester l'attaque à l'aide des testnets Goerli et Mumbai. Si cette section ne vous intéresse pas, passez directement à la conclusion.
Exemple de contrat d'attaquant dans Solidity
Ce sera notre contrat d'attaquant :
Nous allons utiliser ces adresses :
- 0x1491a65c4DE0fEe28f8bFEB185eD76fAB49c5a56 en tant que validateur de pont
- 0x0011fCC414149402DFd8629D71b8823f52d7Ca0B en tant que pirate informatique
- 0xb6Fa6b5Ac9C88AE7a422ac479E798978186B1B60 en tant que contrat d'attaquant
- 0x50287aFea592863C2f30eacFBca54aaC6e85BF02 en tant que
contrat CrossChainWarriors à Goerli (chainA) - 0xCB0fc01dE73aa06F5A443d9F7b682d583E9Aa684 en tant que
contrat CrossChainWarriors à Mumbai (chainB)
Dans chainA, l'attaque semble avoir réussi. Id #1 a été frappé puis brûlé pour le transfert et frappé à nouveau.
Vérifions l'événement CrossChainTransfer :
Le validateur prend l'adresse et le message de l'événement pour exécuter la transaction dans chainB :
message : (96 octets)
0x0000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000b6fa6b5ac9c88ae7a422ac479e798978186 b1b60000000000000000000000000011fcc414149402dfd8629d71b8823f52d7ca0b
Transaction du validateur sur chainB :
Nous pouvons voir que les données d'entrée de la transaction correspondent au message de l'événement (96 octets) :
L'attaque a réussi, l'adresse 0x0011fCC414149402DFd8629D71b8823f52d7Ca0B est le propriétaire de l'identifiant #1 dans les deux chaînes.
Solutions?
Si l'ID de jeton a été incrémenté avant l'appel externe, cette attaque ne serait pas possible.
Une autre solution possible consiste à utiliser le module de contrat ReentrancyGuard d'OpenZeppelin.
Conclusion
Nous avons vu la fonctionnalité de base d'un contrat NFT multichaîne et la surface d'attaque du réglage inter-chaîne de transmission de messages, en nous concentrant sur les appels externes effectués par des bibliothèques sûres. Cette attaque ne serait pas possible dans un environnement à chaîne unique.
Les bonnes pratiques de sécurité doivent être connues et comprises. Vérifiez toujours votre code, le code sûr vient régulièrement avec une extension de la surface d'attaque, ne vous fiez pas aux noms de bibliothèques.
Rapport d'origine :
https://quantumbrief.io/#trusted-by
![Qu'est-ce qu'une liste liée, de toute façon? [Partie 1]](https://post.nghiatu.com/assets/images/m/max/724/1*Xokk6XOjWyIGCBujkJsCzQ.jpeg)



































