cartes sur table

BadReveal – Exploitation NFT

Une nouvelle famille de vulnérabilités

Un nouvel exploit découvert par les chercheurs de Sayfer et @rugpullfinder team permet aux attaquants de savoir quel est le NFT le plus rare avant la révélation du projet. Cela permet à un attaquant un avantage inégal parmi les investisseurs pour acheter la pièce la plus rare et la plus chère. Dans certains projets, le prix le plus rare peut être 50 fois supérieur au prix standard de la pièce.

La vulnérabilité se produit en raison de mauvaises pratiques de codage. Nous avons trouvé la vulnérabilité dans des dizaines de projets. Existent peut-être par milliers que nous n'avons pas encore testés manuellement.

Il n'y a aucun moyen de le tester automatiquement, donc si vous voulez savoir si un projet dans lequel vous souhaitez investir ou dans lequel vous avez déjà investi est vulnérable à BadReveal, merci de nous taguer sur Twitter @SayferSecurity avec #BadReveal et nos chercheurs vont le vérifier.

Affecter plusieurs projets actifs

Pour comprendre l'ampleur du problème, nous avons analysé plus de 100 projets différents, et nous avons constaté que 12 étaient vulnérables à l'attaque. Voici quelques-uns des projets vulnérables : 

CatBloxPUMACapsule (Durée d'exploitation : 2 jours 18 heures, capitalisation boursière : 628.19 ETH)

Everai Heroes : Duo (Durée d'exploitation : 4 jours 2 heures, capitalisation boursière : 1,905.37 XNUMX ETH)

Degen Toonz (Durée d'exploitation : 3 jours 13 heures, capitalisation boursière : 6,221.6 XNUMX ETH)

Plus de détails comme les temps de transaction spécifiques sont dans le annexe.

Détails techniques de la vulnérabilité

Contexte 

Comme les NFT sont Jetons ERC721, chaque jeton est unique avec ses propres métadonnées. Habituellement, dans un projet NFT avec une étape de révélation, les jetons sont créés à l'aveugle, et une fois que tout a été créé, les NFT sont révélés et leurs métadonnées deviennent publiques. Les NFT avec des métadonnées rares pourraient valoir 50 X par rapport aux autres NFT des projets. La plus rare de toutes s'appelle la "Légende".

Mais que se passe-t-il si un attaquant parvient d'une manière ou d'une autre à obtenir les métadonnées avant qu'elles ne soient révélées et utilise ces données pour acheter la légende ? L'attaquant obtient un avantage injuste et gagne plus de profit que tous les autres acheteurs en exploitant ces données pour acheter la légende.

Qu'est-ce qu'un URI de jeton ?

L'URI de jeton d'un NFT est un identifiant unique de ce à quoi « ressemble » le jeton. Un URI peut être une adresse HTTP, un hachage IPFS ou même un blob JSON conservé en chaîne. Il pointe vers un fichier JSON qui contient les métadonnées du jeton.

Lors de la création de votre projet NFT, vous devez vous assurer que les URI de jeton ne sont pas accessibles ou devinables avant la révélation du NFT. Sinon, cela pourrait potentiellement permettre aux attaquants de prendre le contrôle des NFT les plus précieux du projet. En effet, ils pourraient utiliser ces URI pour vérifier certaines propriétés rares dans les métadonnées avant que quiconque ne puisse les voir.

La vulnérabilité 

Nous avons constaté que de nombreux projets définissent l'URI du jeton dans une transaction, puis le révèlent dans une transaction différente. Entre ces deux transactions, qui peuvent parfois prendre des heures, un attaquant peut analyser tous les NFT du projet et trouver celui qui est le plus rare, puis l'acheter en fonction de son tokenID.

Exemple d'exploitation

Ici, nous avons un exemple de fonction qui renvoie l'URI du jeton si le reveal flag est vrai (il est faux par défaut) ou un URI qui pointe vers une image cachée commune à chaque jeton si le drapeau est faux.

function tokenURI(uint256 tokenId) public view virtual override
       returns (string memory)
   {
       require(_exists(tokenId), "Nonexistent token");
       if (reveal == false) {
           return hideURI;
       }
       string memory URI = baseURI;
       uint256 randomId = ((randomNumber + tokenId) % supplyMax) + 1;
       return
           bytes(URI).length > 0
               ? string(abi.encodePacked(URI, randomId.toString(), ".json"))
               : "";
   }

L'URI est créé avec quatre éléments : baseURI, tokenId, supplyMaxet randomNumber. Ces quatre éléments sont publics dans notre code, et nous connaissons facilement les trois premiers éléments mais nous ne savons pas quand randomNumber est initialisé.

Voyons maintenant la fonction où reveal devient vrai.

function revealNFT() external onlyOwner {
    require(randomNumber != 0);
       reveal = true;
   }

Nous comprenons que chaque URI est accessible une fois que les NFT sont révélés. Avant cela, nous n'avions qu'une image générique pour chaque NFT. Mais avant d'appeler revealNFT(), le propriétaire doit s'assurer que randomNumber a été initialisé.

Voyons donc où le code attribue une valeur à randomNumber.

function requestRandomNumbers() external onlyOwner {
       requestId = COORDINATOR.requestRandomWords(
           keyHash,
           s_subscriptionId,
           requestConfirmations,
           callbackGasLimit,
           numWords
       );
   }
 
   function fulfillRandomWords(
       uint256, 
       uint256[] memory randomNumbers
   ) internal override {
       randomNumber = randomNumbers[0];
   }

Dans cet exemple, ils utilisent VRF de Chainlink, un générateur de nombres aléatoires hors chaîne, pour créer un décalage dans les URI avec randomNumber. Ces deux fonctions ci-dessus montrent comment cela fonctionne. Le propriétaire devra appeler requestRandomNumbers() qui attribuera un nombre aléatoire à randomNumber avec un appel interne de fulfillRandomWords() par Chainlink.

Que se passe-t-il dans la vraie vie lorsque le propriétaire veut révéler les NFT ?

Le propriétaire commencera par appeler requestRandomNumbers() attribuer une valeur aléatoire à randomNumber. Car randomNumber a maintenant une valeur non nulle, le propriétaire peut appeler revealNFT() pour tourner le reveal drapeau en vrai, puis tous les URI sont publics avec tokenURI().

Mais rappelez-vous que pour prédire les URI, nous n'avons besoin que de quatre éléments dont trois déjà connus et randomNumber qui est également public et a sa valeur attribuée lorsque requestRandomNumbers() est appelé.

Alors en attendant entre requestRandomNumbers() et revealNFT() sont appelées, les URI ne sont pas encore publiques mais nous avons tous les éléments pour les prédire.

Une autre version de cette vulnérabilité existe. Bien que plus courant, il est plus difficile à exploiter. S'il n'y a pas d'étape de révélation, le baseURI est souvent défini au début ou au milieu de la phase de menthe. Ainsi dès qu'un NFT est frappé, il est révélé, mais les jetons restants restent inconnus. Nous pouvons alors récupérer l'URI et l'utiliser pour connaître les NFT les plus rares parmi ceux qui restent. Ensuite, vous devez essayer de frapper au bon moment pour les trouver.

Comme nous l'avons dit plus haut, le résultat est que les attaquants peuvent prendre le contrôle des NFT les plus précieux du projet car ils pourraient utiliser ces URI pour vérifier certaines propriétés rares dans les métadonnées avant que quiconque ne puisse les voir.

Pour éviter d'avoir le même genre de vulnérabilités, vous pouvez vérifier ces Stratégies de randomisation pour les gouttes NFT Par William Entrike. Cela vous aidera à randomiser les URI de jeton en toute sécurité.

Atténuation

Alors, que devez-vous faire si vous possédez un projet NFT et que vous souhaitez atténuer les risques et protéger la base de code de BadReveal ?

Étant donné que l'essence de la vulnérabilité BadReveal repose sur l'avantage que l'attaquant a entre la définition de la transaction d'URI de jeton sur la transaction de révélation (ou mint), l'atténuation consisterait à combiner les deux en une seule transaction.

Si vous prévoyez de révéler les NFT, définissez l'URI du jeton de base en même temps :

 function reveal(string memory baseURI) public onlyOwner {
     revealed = true;
     baseTokenURI = baseURI;
 }

Dans l'exemple que nous avons vu précédemment, la solution pourrait également être de déplacer la révélation pour qu'elle soit automatiquement définie par la transaction de Chianlink :

function fulfillRandomness(bytes32, uint256 _randomness) internal override {
     revealed = true;
     randomNumber = randomNumbers[0];
}

En tirant parti de cette approche, vous voudrez peut-être envisager de jeter le revealed booléen de votre code et le remplacer par une vérification à la volée du Chainlink randomNumber affectation:

function someMintingFunction(uint256 _quantity) internal {
     require(!randomNumber, "Not revealed yet");
     //…
}

L'utilisation de ce mécanisme est le moyen le plus simple d'atténuer BadReveal car il n'introduit pas l'écart entre les transactions qui permet à un attaquant d'acheter tôt avant que d'autres acheteurs n'obtiennent les informations de métadonnées.

Résumé

Les cyber-risques des projets NFT peuvent prendre de nombreuses formes et formes, il est communément admis que si votre NFT n'a pas été volé, vous n'avez pas été piraté, mais que se passerait-il si vous étiez censé obtenir le NFT le plus rare dans le jeu, mais un attaquant était assez intelligent pour l'acheter avant vous? Vous vous êtes fait pirater sans même le savoir.

Il est important de définir des exigences pour que les projets NFT investissent dans des audits de haute qualité et utilisent des pratiques de sécurité bien. 

Nous sommes convaincus que cette vulnérabilité est couramment exploitée à l'état sauvage sous le radar des investisseurs et des communautés NFT. Veuillez partager ces informations avec autant de projets que possible afin qu'ils assurent leur sécurité.

Nous tenons à remercier recherche de tapis @rugpullfinder l'aide pour enquêter et vérifier la vulnérabilité avec nous. Nous avons utilisé leur expertise et leurs connaissances dans l'espace NFT pour approfondir le terrier du lapin de BarReveal

Annexe – Projet affecté

Détails de CatBloxPUMACapsule

https://etherscan.io/address/0x8b8D1225bB21CA07812FF3c2ee9358f7b5d90EcA

Everai Heroes : Détails du duo

https://etherscan.io/address/0x9a38dec0590abc8c883d72e52391090e948ddf12

Degen Toonz Détails

https://etherscan.io/address/0x19b86299c21505cdf59ce63740b240a9c822b5e4

Écrit Par
Avigdor Sason Cohen

Avigdor est chercheur en sécurité web3 chez Sayfer. Il est passionné par les nouvelles technologies blockchain et comment nous pourrions nous assurer de les développer de manière sécurisée.

 

Passer au contenu