Auteur : CertiK

 

Auparavant, l'équipe CertiK avait découvert une série de vulnérabilités de déni de service dans la blockchain Sui. Parmi ces vulnérabilités, une nouvelle vulnérabilité à fort impact se démarque. Cette vulnérabilité peut empêcher les nœuds du réseau Sui de traiter de nouvelles transactions, ce qui équivaut à un arrêt complet de l'ensemble du réseau.

Lundi dernier, CertiK a reçu une prime de bug SUI de 500 000 $ pour avoir découvert cette vulnérabilité de sécurité majeure. CoinDesk, le média faisant autorité dans l'industrie américaine, a rendu compte de l'incident, puis les principaux médias ont suivi son rapport et publié des informations pertinentes.

Cette vulnérabilité de sécurité est clairement appelée « roue de hamster » : sa méthode d'attaque unique est différente des attaques actuellement connues. L'attaquant n'a besoin que de soumettre une charge utile d'environ 100 octets pour déclencher une boucle infinie dans le nœud de vérification Sui, ce qui le rend incapable. pour répondre à de nouvelles transactions.

De plus, les dommages causés par l'attaque peuvent persister après le redémarrage du réseau et se propager automatiquement dans le réseau Sui, laissant tous les nœuds comme un hamster fonctionnant sans fin sur la roue, incapables de traiter de nouvelles transactions. C'est pourquoi nous appelons ce type d'attaque unique une attaque « roue de hamster ».

Après avoir découvert la vulnérabilité, CertiK l'a signalée à Sui via le programme de bug bounty de Sui. Sui a également réagi efficacement dans les plus brefs délais, a confirmé la gravité de la vulnérabilité et a activement pris les mesures correspondantes pour réparer le problème avant le lancement du réseau principal. En plus de corriger cette vulnérabilité spécifique, Sui a mis en œuvre des mesures d'atténuation préventives pour réduire les dommages potentiels que cette vulnérabilité pourrait causer.

Afin de remercier l'équipe CertiK pour sa divulgation responsable, Sui a accordé à l'équipe CertiK un bonus de 500 000 $ US.

Les détails techniques suivants de cette vulnérabilité critique seront divulgués pour clarifier la cause profonde et l'impact potentiel de la vulnérabilité.

Explication détaillée des vulnérabilités

Le rôle clé des validateurs dans Sui

Pour les blockchains basées sur le langage Move telles que Sui et Aptos, le mécanisme de garantie pour empêcher les attaques malveillantes de charge utile est principalement la technologie de vérification statique. Grâce à la technologie de vérification statique, Sui peut vérifier la validité de la charge utile soumise par les utilisateurs avant la publication ou la mise à niveau du contrat. Le validateur fournit une série de vérificateurs pour garantir l'exactitude de la structure et de la sémantique. Ce n'est qu'après avoir réussi les contrôles et les vérifications que le contrat entrera dans la machine virtuelle Move pour exécution.

Menaces de charge utile malveillantes sur la chaîne Move

La chaîne Sui fournit un nouvel ensemble de modèles et d'interfaces de stockage en plus de la machine virtuelle Move d'origine, Sui dispose donc d'une version personnalisée de la machine virtuelle Move. Afin de prendre en charge les nouvelles primitives de stockage, Sui introduit en outre une série de contrôles supplémentaires personnalisés pour la vérification de la sécurité des charges utiles non fiables, telles que la sécurité des objets et l'accès au stockage global. Ces contrôles personnalisés correspondent aux fonctionnalités uniques de Sui, nous les appelons donc des validateurs Sui.

Ordre de Sui de vérifier la charge

Comme le montre la figure ci-dessus, la plupart des contrôles du validateur effectuent une vérification de sécurité au niveau structurel par rapport au CompiledModule (qui représente l'exécution de la charge utile du contrat fournie par l'utilisateur). Par exemple, utilisez le « Vérificateur de doublons » pour vous assurer qu'il n'y a pas d'entrées en double dans la charge utile d'exécution ; utilisez le « Vérificateur de limites » pour vous assurer que la longueur de chaque champ dans la charge utile d'exécution est comprise dans la limite d'entrée autorisée.

En plus des vérifications au niveau structurel, la vérification statique du vérificateur nécessite encore des méthodes d'analyse plus complexes pour garantir la robustesse de la charge utile non fiable au niveau sémantique.

Découvrez l’interpréteur abstrait de Move :

Analyse linéaire et itérative

L'interpréteur abstrait fourni par Move est un framework spécialement conçu pour effectuer une analyse de sécurité complexe sur le bytecode via une interprétation abstraite. Ce mécanisme rend le processus de vérification plus raffiné et précis, et chaque vérificateur est autorisé à définir son état abstrait unique pour l'analyse.

Lors du démarrage d'une exécution, l'interpréteur abstrait construit un graphe de flux de contrôle (CFG) à partir des modules compilés. Chaque bloc de base de ces CFG maintient un ensemble d'états, à savoir « état de pré-commande » et « état de post-commande ». L'« état de pré-commande » fournit un instantané de l'état du programme avant l'exécution du bloc de base, tandis que « l'état de post-commande » fournit une description de l'état du programme après l'exécution du bloc de base.

Lorsque l'interpréteur abstrait ne rencontre aucun saut (ou boucle) dans le graphe de flux de contrôle, il suit un principe d'exécution linéaire simple : chaque bloc de base est analysé tour à tour et l'instruction précédente est calculée sur la base de la sémantique de chaque instruction du bloc. état séquentiel et état post-séquentiel. Le résultat est un instantané précis de chaque état de base de bloc d'un programme pendant l'exécution, aidant à vérifier les propriétés de sécurité du programme.

Déplacer le flux de travail de l'interprète abstrait

Cependant, ce processus devient plus compliqué lorsqu’il existe des boucles dans le flux de contrôle. L'apparition d'une boucle signifie que le graphe de flux de contrôle contient un bord de rebond. La source du bord de rebond correspond à l'état ultérieur du bloc de base actuel, et le bloc de base cible (tête de boucle) du bord de rebond est un état précédemment analysé. L'état de précommande du bloc de base, l'interpréteur abstrait doit donc fusionner soigneusement les états des deux blocs de base liés au saut.

Si l'état fusionné s'avère différent de l'état de précommande existant du bloc de base de tête de boucle, l'interpréteur abstrait met à jour l'état du bloc de base de tête de boucle et redémarre l'analyse à partir de ce bloc de base. Ce processus d'analyse itératif se poursuit jusqu'à ce que le pré-état de la boucle soit stable. En d’autres termes, ce processus est répété jusqu’à ce que l’état de précommande du bloc de base en tête de boucle ne change plus entre les itérations. Atteindre un point fixe indique que l’analyse de la boucle est terminée.

Validateur IDLeak suivant :

Analyse d'interprétation abstraite personnalisée

Contrairement à la conception originale de Move, la plate-forme blockchain de Sui introduit un modèle de stockage global unique centré sur les « objectifs ». Une caractéristique notable de ce modèle est que toute structure de données avec un attribut clé (stocké sous forme d'index sur la chaîne) doit avoir le type ID comme premier champ de la structure. Le champ ID est immuable et ne peut pas être transféré vers d'autres cibles car chaque objet doit avoir un ID globalement unique. Pour garantir ces propriétés, Sui a construit un ensemble de logique d'analyse personnalisée au-dessus de l'interpréteur abstrait.

Le vérificateur IDLeak, également connu sous le nom d'id_leak_verifier, fonctionne en conjonction avec l'interpréteur abstrait pour effectuer une analyse. Il possède son propre AbstractDomain unique, appelé AbstractState. Chaque AbstractState se compose de AbstractValue correspondant à plusieurs variables locales. Surveillez l'état de chaque variable locale via AbstractValue pour savoir si une variable ID est nouvelle.

Pendant le processus d'empaquetage de la structure, le validateur IDLeak permet uniquement d'emballer un nouvel identifiant dans une structure. Grâce à une analyse d'interprétation abstraite, le validateur IDLeak peut suivre de manière exhaustive l'état du flux de données local pour garantir qu'aucun identifiant existant n'est transféré vers d'autres objets de structure.

Problème d'incohérence de maintenance de l'état du validateur Sui IDLeak

Le validateur IDLeak est intégré à l'interpréteur abstrait Move en implémentant la fonction AbstractState::join. Cette fonction joue un rôle essentiel dans la gestion des états, notamment dans la fusion et la mise à jour des valeurs d'état.

Examinez ces fonctions en détail pour comprendre leur fonctionnement :

Dans AbstractState::join, la fonction prend un autre AbstractState en entrée et tente de fusionner son état local avec l'état local de l'objet actuel. Pour chaque variable locale dans l'état d'entrée, il compare la valeur de la variable à sa valeur actuelle dans l'état local (si elle n'est pas trouvée, la valeur par défaut est AbstractValue :: Other). Si les deux valeurs ne sont pas égales, il définira un indicateur « modifié » comme base pour savoir si le résultat final de la fusion de l'état a changé et mettra à jour la valeur de la variable locale dans l'état local en appelant AbstractValue :: join.

Dans AbstractValue::join, la fonction compare sa valeur avec une autre AbstractValue. S'ils sont égaux, la valeur transmise sera renvoyée. S’il n’est pas égal, AbstractValue :: Other est renvoyé.

Cependant, cette logique de maintenance d’état contient un problème d’incohérence caché. Bien que AbstractState::join renvoie un résultat indiquant que l'état fusionné a changé (JoinResult::Changed) en fonction de la différence entre l'ancienne et la nouvelle valeur, la valeur de l'état mis à jour fusionné peut toujours rester inchangée.

Ce problème d'incohérence est dû à l'ordre des opérations : la détermination de l'état modifié dans AbstractState::join se produit avant la mise à jour de l'état (AbstractValue::join), et cette détermination ne reflète pas le résultat de la mise à jour de l'état réel.

De plus, dans AbstractValue::join, AbstractValue::Other joue un rôle déterminant dans le résultat de la fusion. Par exemple, si l'ancienne valeur est AbstractValue::Other et la nouvelle valeur est AbstractValue::Fresh, la valeur d'état mise à jour est toujours AbstractValue::Other, même si les anciennes et les nouvelles valeurs sont différentes, l'état lui-même ne le fait pas. changer après la mise à jour.

Exemple : incohérence dans les connexions d'état

Cela introduit une incohérence : le résultat de la fusion des états de bloc de base est jugé comme "modifié", mais la valeur de l'état fusionné elle-même n'a pas changé. Dans le processus d’interprétation et d’analyse abstraites, la survenance de telles incohérences peut avoir de graves conséquences. Nous passons en revue le comportement de l'interpréteur abstrait lorsque des boucles se produisent dans un graphe de flux de contrôle (CFG) :

Lorsqu'une boucle est rencontrée, l'interpréteur abstrait utilise une méthode d'analyse itérative pour fusionner l'état du bloc de base cible du saut avec le bloc de base actuel. Si l'état fusionné change, l'interpréteur abstrait réanalysera à partir de la cible de saut.

Cependant, si l'opération de fusion de l'analyse d'interprétation abstraite marque par erreur le résultat de la fusion d'état comme « modifié » alors qu'en fait la valeur de la variable interne de l'état n'a pas changé, cela conduira à une réanalyse sans fin et créera une boucle infinie. .

exploiter davantage l'incohérence

Déclencher une boucle infinie dans le validateur Sui IDLeak

En exploitant cette incohérence, un attaquant peut construire un graphe de flux de contrôle malveillant pour tromper le validateur IDLeak dans une boucle infinie. Ce graphique de flux de contrôle soigneusement construit se compose de trois blocs de base : BB1 et BB2, BB3. Il convient de noter que nous avons intentionnellement introduit une arête de saut de BB3 à BB2 pour construire une boucle.

Un statut CFG+ malveillant peut conduire à une boucle infinie au sein du validateur IDLeak.

Le processus commence par BB2, où la AbstractValue d'une variable locale spécifique est définie sur ::Other. Après avoir exécuté BB2, le processus est transféré vers BB3, où la même variable est définie sur ::Fresh. À la fin de BB3, il y a un bord de saut qui passe à BB2.

Dans le processus d’interprétation abstraite et d’analyse de cet exemple, l’incohérence mentionnée précédemment joue un rôle clé. Lorsque le front de rebond est traité, l'interpréteur abstrait tente de connecter l'état de post-ordre de BB3 (la variable est "::Fresh") avec l'état de pré-ordre de BB2 (la variable est "::Other"). La fonction AbstractState::join a remarqué la différence entre les anciennes et les nouvelles valeurs et a défini l'indicateur "change" pour indiquer que BB2 doit être réanalysé.

Cependant, le comportement dominant de "::Other" dans AbstractValue::join signifie qu'après la fusion de AbstractValue, la valeur réelle de la variable d'état BB2 est toujours "::Other" et le résultat de la fusion d'état n'a pas changé.

Ainsi, une fois que ce processus cyclique démarre, c'est-à-dire pendant que le validateur continue de réanalyser BB2 et tous ses nœuds de bloc de base successeurs (BB3 dans ce cas), il se poursuit indéfiniment. La boucle infinie consomme tous les cycles CPU disponibles, la rendant incapable de traiter les réponses aux nouvelles transactions, et cette situation persiste après le redémarrage du validateur.

En exploitant cette vulnérabilité, les nœuds validateurs fonctionnent dans une boucle infinie comme un hamster courant sans fin sur une roue, incapable de traiter de nouvelles transactions. C'est pourquoi nous appelons ce type d'attaque unique une attaque « roue de hamster ».

Une attaque de type « roue de hamster » peut effectivement paralyser le validateur Sui, paralysant ainsi l'ensemble du réseau Sui.

Après avoir compris la cause et le processus de déclenchement de la vulnérabilité, nous avons construit un exemple spécifique en utilisant la simulation de bytecode Move suivante et avons réussi à déclencher la vulnérabilité dans la simulation dans l'environnement réel :

Cet exemple montre comment déclencher la vulnérabilité dans un environnement réel grâce à un bytecode soigneusement construit. Plus précisément, un attaquant peut déclencher une boucle infinie dans le validateur IDLeak, en utilisant une charge utile d'environ 100 octets pour consommer tous les cycles CPU d'un nœud Sui, empêchant ainsi le traitement de nouvelles transactions et provoquant un déni de service sur le réseau Sui. .

Le préjudice persistant des attaques « roue de hamster » dans le réseau Sui

Le programme de bug bounty de Sui a des réglementations strictes sur l'évaluation des niveaux de vulnérabilité, principalement basées sur le degré de dommage causé à l'ensemble du réseau. Une vulnérabilité qui répond à la note « critique » doit arrêter l'ensemble du réseau et empêcher efficacement la confirmation de nouvelles transactions, et nécessite un hard fork pour résoudre le problème ; si la vulnérabilité ne peut qu'amener certains nœuds du réseau à refuser le service, ce sera le cas ; classé comme « risque moyen » au maximum (moyen) » ou « élevé ».

La vulnérabilité « roue de hamster » découverte par l'équipe CertiK Skyfall peut arrêter l'ensemble du réseau Sui et nécessite la sortie officielle d'une nouvelle version pour la mettre à niveau et la réparer. Compte tenu de la gravité de la vulnérabilité, Sui l'a finalement qualifiée de « critique ». Afin de mieux comprendre l'impact sérieux de l'attaque de la « roue de hamster », nous devons comprendre l'architecture complexe du système backend de Sui, en particulier l'ensemble du processus de publication ou de mise à niveau des transactions en chaîne.

Aperçu des interactions pour la soumission de transactions dans Sui

Initialement, les transactions des utilisateurs sont soumises via RPC frontal et transmises au service back-end après une vérification de base. Le service backend Sui est chargé de valider davantage la charge utile de la transaction entrante. Après avoir vérifié avec succès la signature de l'utilisateur, la transaction est convertie en un certificat de transaction (contenant les informations de transaction ainsi que la signature de Sui).

Ces certificats de transaction constituent un élément fondamental du fonctionnement du réseau Sui et peuvent être propagés entre différents nœuds de vérification du réseau. Pour les transactions de création/mise à niveau de contrat, le nœud de vérification appellera le validateur Sui pour vérifier et vérifier la validité de la structure/sémantique du contrat de ces certificats avant de pouvoir les mettre sur la chaîne. C’est lors de cette phase critique de vérification que la vulnérabilité « boucle infinie » peut être déclenchée et exploitée.

Lorsque la vulnérabilité est déclenchée, le processus de vérification est interrompu indéfiniment, ce qui entrave effectivement la capacité du système à traiter de nouvelles transactions et provoque un arrêt complet du réseau. Pire encore, la situation persiste après le redémarrage du nœud, ce qui signifie que les mesures d’atténuation traditionnelles sont loin d’être suffisantes. Une fois cette vulnérabilité déclenchée, des « dommages continus » se produiront, laissant un impact durable sur l'ensemble du réseau Sui.

La solution de Sui

Après les commentaires de CertiK, Sui a rapidement confirmé la vulnérabilité et publié un correctif pour corriger la faille critique. Le correctif garantit la cohérence entre les changements d'état et les indicateurs post-changement, éliminant ainsi l'impact critique des attaques de type « roue de hamster ».

Pour éliminer l'incohérence ci-dessus, le correctif de Sui inclut un ajustement petit mais critique à la fonction AbstractState::join. Ce correctif supprime la logique de détermination du résultat de la fusion d'état avant d'exécuter AbstractValue::join. Au lieu de cela, il exécute d'abord la fonction AbstractValue::join pour effectuer la fusion d'état et définit si la fusion se produit en comparant le résultat final de la mise à jour avec l'état d'origine. valeur (old_value). Changer la marque.

De cette façon, le résultat de la fusion d’états sera cohérent avec le résultat de la mise à jour réelle et aucune boucle infinie ne se produira pendant le processus d’analyse.

En plus de corriger cette vulnérabilité spécifique, Sui a déployé des mesures d'atténuation pour réduire l'impact des futures vulnérabilités du validateur. Selon la réponse de Sui dans le rapport de bug, l'atténuation implique une fonctionnalité appelée Denylist.

"Cependant, les validateurs disposent d'un fichier de configuration de nœud qui leur permet de rejeter temporairement certaines catégories de transactions. Cette configuration peut être utilisée pour désactiver temporairement le traitement des versions et des mises à niveau de packages. En raison de ce bug, il est nécessaire d'exécuter Sui avant de signer une version ou un package. mettre à niveau le tx alors qu'une liste de refus empêchera le validateur de s'exécuter et supprimera le tx malveillant, refuser temporairement la liste de ces types de tx est une atténuation efficace à 100 % (bien que cela perturbera temporairement le service pour toute personne essayant de publier ou de mettre à niveau le code).

À propos, nous disposons de ce fichier de configuration de liste de refus TX depuis un certain temps, mais nous avons également ajouté un mécanisme similaire pour les certificats en guise d'atténuation de suivi pour la vulnérabilité "boucle infinie du validateur" que vous avez signalée précédemment. Avec ce mécanisme en place, nous aurons plus de flexibilité avec cette attaque : nous utiliserons la configuration de la liste de refus de certificat pour faire oublier au validateur les mauvais certificats (rompant la boucle infinie), et la configuration de la liste de refus TX pour désactiver les versions/mises à niveau. empêchant ainsi la création de nouvelles transactions d’attaque malveillantes. Merci de nous avoir fait réfléchir à cela !

Un validateur dispose d'un nombre limité de « ticks » (contrairement au gas) pour la vérification du bytecode avant de signer une transaction, si tout le bytecode émis dans une transaction ne peut pas être vérifié en autant de ticks, le validateur refusera de signer la transaction, l'empêchant de s'exécutant sur le réseau. Auparavant, la mesure ne fonctionnait que pour un ensemble sélectionné de passes de validation complexes. Pour résoudre ce problème, nous étendons la mesure à chaque validateur pour garantir qu'il existe une contrainte sur le travail effectué par le validateur pendant le processus de vérification de chaque tick. Nous avons également corrigé un bug potentiel de boucle infinie dans le validateur de fuite d'identification. "

--Instructions des développeurs Sui sur les corrections de bugs

Dans l'ensemble, Denylist permet aux validateurs de contourner temporairement les exploits des validateurs et de prévenir efficacement les dommages potentiels causés par certaines transactions malveillantes en désactivant le processus de publication ou de mise à niveau. Lorsque les mesures d'atténuation de Denylist entrent en vigueur, les nœuds garantissent qu'ils peuvent continuer à travailler en sacrifiant leurs propres fonctions contractuelles de publication/mise à jour.

Résumer

Dans cet article, nous partageons les détails techniques de l'attaque "hamster wheel" découverte par l'équipe CertiK Skyfall, expliquant comment ce nouveau type d'attaque exploite des vulnérabilités clés pour provoquer l'arrêt complet du réseau Sui. En outre, nous avons également examiné de plus près la réponse rapide de Sui pour résoudre ce problème critique et partagé le correctif de vulnérabilité et les méthodes d'atténuation ultérieures pour des vulnérabilités similaires.