Auteur : Chengdu Lian’an
Original : « Recherche sur la sécurité des ponts inter-chaînes (3) » | Dialyse sécurisée Polygon Warrior, comment empêcher l’ouverture de la « boîte de Pandore » ?
Bienvenue dans la série d’articles sur la « Recherche sur la sécurité des ponts inter-chaînes » produite par Chengdu Chain Security, dans l’article précédent (Recherche sur la sécurité des ponts inter-chaînes (2) | Qu’est-ce que le premier braquage décentralisé du pont Nomad nous apporte ?), nous effectuons une analyse technique professionnelle du protocole du pont Nomad en détail.
Aujourd’hui, l’équipe de recherche sur la sécurité de la chaîne de Chengdu effectuera une dialyse en toute sécurité de Polygon, un guerrier polygonal, alors continuez à lire.
Qui est 1_Polygon ?
Polygon est la solution de mise à l’échelle de couche 2 d’Ethereum, dont la vision est de construire l’Internet blockchain d’Ethereum. Polygon fournit un cadre commun qui permet aux développeurs de tirer parti de la sécurité d’Ethereum pour créer des chaînes personnalisées et axées sur les applications et fournir un réseau interopérable qui combine une variété de schémas de mise à l’échelle différents tels que : zk-rollup, PoS, etc. Parmi eux, Polygon PoS est actuellement la solution de mise à l’échelle la plus mature et la plus connue sur Polygon. Il utilise la sidechain pour le traitement des transactions afin d’améliorer la vitesse des transactions et d’économiser la consommation de gaz, et la structure du réseau comprend principalement les trois couches suivantes :
Couche Ethereum :
Une série de contrats sur le réseau principal Ethereum, comprenant principalement : les contrats de jalonnement, de point de contrôle et de récompense, sont responsables des fonctions de gestion du jalonnement liées aux mises PoS, notamment : fournir la fonction de jalonnement du jeton natif MATIC, afin que toute personne qui jalonne le jeton puisse rejoindre le système en tant que validateur, vérifier la conversion du réseau Polygon pour obtenir des récompenses de jalonnement, punir les validateurs pour les doubles signatures, les temps d’arrêt des validateurs et autres comportements illégaux, et sauver les points de contrôle.
Couche de Heimdall :
La couche de validation de preuve d’enjeu, qui se compose d’un ensemble de nœuds PoS Heimdall, est chargée de soumettre les points de contrôle du réseau Polygon au réseau principal Ethereum tout en écoutant un ensemble de contrats de jalonnement déployés sur Ethereum. Le processus principal est le suivant : tout d’abord, sélectionnez un sous-ensemble de validateurs actifs dans le pool de validateurs en tant que producteurs de blocs, qui seront responsables de la création de blocs au niveau de la couche Bor et de leur diffusion, puis validez le hachage racine de Merkle et ajoutez des signatures en fonction des points de contrôle soumis par Bor, et enfin, le proposant sera responsable de la collecte de toutes les signatures de validateurs pour le point de contrôle spécifié, et si le nombre de signatures atteint plus de 2/3, le point de contrôle sera soumis sur Ethereum.
Bor Layer :
La couche de producteur de blocs, qui se compose d’un groupe de producteurs de blocs régulièrement sélectionnés par un comité de validateurs sur la couche de Heimdall, est un sous-ensemble de validateurs responsables de l’agrégation des transactions sur la sidechain Polygon et de la génération de blocs. Cette couche publie périodiquement des points de contrôle dans la couche de Heimdall, où le point de contrôle représente un instantané de la chaîne de Bor, comme illustré dans l’image ci-dessous.
2_Polygon Interopérabilité
2.1 Point de contrôle
Le mécanisme de point de contrôle est un mécanisme permettant de synchroniser les données de la couche Bor avec Ethereum, où les données synchronisées sont un point de contrôle, c’est-à-dire un instantané des données de bloc de la couche Bor contenues dans un intervalle de point de contrôle, le code source est le suivant :
Proposant : Le soumissionnaire, qui est également sélectionné par les validateurs, les producteurs de blocs et les proposants sont un sous-ensemble de validateurs, et leurs responsabilités sont déterminées par leur participation dans le pool global
RootHash : est un hachage de Merkle généré à partir du bloc Bor entre StartBlock et EndBlock
Ce qui suit est un pseudo-code pour le bloc Bor numéroté de 1 à n pour générer la valeur RootHash :
En résumé, cette valeur est la valeur de hachage racine de l’arbre de Merkel, qui est composée du numéro de bloc dans l’en-tête de bloc Bor, de l’horodatage du bloc, de la valeur de hachage racine de l’arborescence des transactions tx hash et de la valeur de hachage keccak256 calculée à partir du hachage racine de l’arbre de réception.
AccountRootHash : un hachage Merkle des informations de compte liées au validateur qui doit être envoyé à chaque point de contrôle sur Ethereum, et la valeur de hachage des informations de compte individuelles est calculée comme suit :
Le AccountRootHash est généré à partir du hachage racine de l’arborescence Merkle du compte de la même manière que la valeur RootHash.
2.2 StateSync
StateSync fait référence à la synchronisation des données Ethereum avec la chaîne Polygon Matic, qui est principalement divisée en étapes suivantes :
Tout d’abord, le contrat sur Ethereum déclenchera la fonction syncState() dans StateSender.sol pour la synchronisation de l’état
La fonction syncState() émettra un événement d’événement comme suit :
Tous les validateurs de la couche Heimdall recevront l’événement, et l’un des validateurs empaquetera la transaction dans le bloc heimdall et l’ajoutera à la liste de synchronisation d’état en attente ;
Le nœud de la couche bor obtiendra la liste ci-dessus à synchroniser via l’API et la remettra au contrat de la couche bor pour un traitement ultérieur de la logique métier.
2.3 Pont polygonal
Polygon Bridge permet un canal inter-chaînes bidirectionnel entre Polygon et Ethereum, ce qui permet aux utilisateurs de transférer plus facilement des tokens entre deux plateformes de chaînes différentes sans menaces tierces ni contraintes de liquidité du marché. Il existe deux types de Polygon Bridge, PoS et Plasma, et les deux présentent les similitudes suivantes dans le transfert d’actifs entre Polygon et Ethereum :
Tout d’abord, vous devez mapper le jeton sur Ethereum à Polygon, comme indiqué dans l’image ci-dessous :
La technologie d’ancrage bidirectionnel (Two-way Peg) est également utilisée, c’est-à-dire
a : Tous les actifs de tokens transférés d’Ethereum seront d’abord verrouillés sur Ethereum, et le même nombre de tokens mappés sera frappé sur Polygon ;
b : Afin de retirer des actifs de tokens sur Ethereum, vous devez d’abord brûler ces tokens mappés sur Polygon, puis déverrouiller les actifs verrouillés sur Ethereum ;
La figure suivante montre la comparaison entre PoS Bridge et Plasma Bridge :
Comme on peut le voir sur la figure ci-dessus, en termes de sécurité, PoS Bridge s’appuie sur la sécurité de l’ensemble de validateurs externes, tandis que Plasma s’appuie sur la sécurité de la chaîne principale Ethereum. Dans le même temps, lorsque les utilisateurs effectuent des transferts d’actifs inter-chaînes (tels que le transfert de tokens de Polygon vers Ethereum), le PoS ne nécessite qu’un intervalle de point de contrôle, d’environ 20 minutes à 3 heures, tandis que Plasma nécessite une période de contestation de 7 jours. Dans le même temps, PoS prend en charge davantage de jetons standard, tandis que Plasma ne prend en charge que trois types, à savoir : ETH, ERC20, ERC721.
Messagerie à 3 _Cross chaînes—PoS Bridge
Le pont PoS se compose principalement de deux fonctions : le dépôt fait référence au transfert des actifs des utilisateurs sur Ethereum vers Polygon, et les retraits font référence au retrait d’actifs de Polygon vers Ethereum.
Dépôt
Voici un exemple d’utilisateur Alice utilisant PoS Bridge pour envoyer des actifs symboliques de son compte Ethereum à son compte Polygon :
Si les actifs de jeton que vous souhaitez transférer sont ERC20, ERC721 ou ERC1155, vous devez autoriser le jeton que vous souhaitez transférer via la fonction approtect. Comme indiqué ci-dessous, le nombre de tokens correspondant est autorisé au contrat erc20Prefer en appelant la méthode approtect dans le contrat de token sur Ethereum.
La fonction approtect a deux paramètres :
Spender : l’adresse de destination où l’utilisateur autorise la dépense de tokens
amount : Le nombre de jetons qui peuvent être dépensés
Une fois la transaction d’autorisation ci-dessus confirmée, l’utilisateur verrouille ensuite le jeton dans le contrat préliminaire erc20 sur Ethereum en appelant la méthode depositFor() du contrat RootChainManager. Ici, si le type d’actif à transférer est ETH, depositEtherFor() est appelé. Les détails sont les suivants :
La fonction depositFor comporte trois paramètres :
user : L’adresse de l’utilisateur qui a reçu les tokens de dépôt sur Polygon
rootToken : L’adresse du jeton sur la chaîne principale d’Ethereum
depositData : Le nombre de tokens encodés par ABI
Voici le code spécifique de la fonction depositFor dans le contrat RootChainManager :
En analysant le code source, on peut voir que la fonction obtient d’abord l’adresse de contrat prédicat correspondant au jeton, puis appelle sa fonction lockTokens() pour verrouiller le jeton dans le contrat. Enfin, syncState() sera appelé pour la synchronisation d’état par _stateSender, qui ne peut être appelé que par l’expéditeur d’état défini par admin.
La fonction syncState() de StateSender.sol soumettra l’événement StateSynced, plus précisément :
Le premier paramètre est l’index du numéro de séquence du journal, le deuxième paramètre est utilisé pour vérifier si l’appelant est une adresse de contrat légitime enregistrée et le troisième paramètre est les données qui doivent être synchronisées avec l’état. La transaction est ajoutée au bloc Heimdall et à la liste de synchronisation des états en attente.
Ensuite, une fois que le nœud bor de la chaîne Polygon Matic a obtenu l’événement StateSynced dans la liste de synchronisation d’état via l’API, le contrat ChildChainManager de la chaîne appellera la fonction onStateReceive(), qui est utilisée pour recevoir les données de synchronisation téléchargées à partir d’Ethereum, et passera à l’étape suivante en fonction du type de logique métier de la synchronisation d’état :
data : bytes32 syncType et bytes syncData. Lorsque syncType est mappé, syncData est l’adresse rootToken codée, l’adresse childToken et bytes32 tokenType, et lorsque syncType est deposit, syncData est l’adresse utilisateur codée. L’adresse rootToken et depositData du type bytes. depositData est la quantité dans REC20 et tokenId dans ERC721.
Puisqu’il s’agit d’une activité de dépôt, la fonction _syncDeposit() sera appelée. Cette fonction va d’abord décoder le syncData dans le format correspondant pour obtenir le rootToken, l’adresse de l’utilisateur et le depositData correspondants. Vérifiez ensuite si le rootToken a un jeton de mappage correspondant sur le polygone, et appelez la fonction deposit() du childToken s’il y en a un.
Ici, nous prenons le contrat de jeton ERC20 comme exemple pour présenter comment déposer le contrat de jeton de mappage. Cette fonction transfère le nombre correspondant de jetons sur le compte de l’utilisateur.
La fonction a deux paramètres :
user : L’adresse de l’utilisateur qui effectue un dépôt
depositData : Le montant encodé en ABI
Retraits
Voici un exemple d’utilisateur Alice utilisant PoS Bridge pour retirer les fonds déposés sur son compte Polygon vers son compte Ethereum :
Lorsque l’utilisateur se retire, il doit brûler le nombre correspondant de jetons mappés en appelant la fonction withdraw() du contrat de jeton de mappage sur la chaîne Polygon.
Retirer ne contient qu’un seul paramètre : le nombre de jetons qui seront brûlés. La fonction withdraw() dans le contrat de token correspondant est la suivante :
Les transactions ci-dessus seront incluses dans le point de contrôle après environ 20 minutes à 3 heures, et le validateur les soumettra à Ethereum.
Une fois la transaction ajoutée au point de contrôle et soumise à Ethereum, la fonction exit() du contrat RootChainManager sur Ethereum sera appelée, ce qui confirmera la validité de la transaction de retrait sur Polygon en vérifiant le contenu du point de contrôle soumis, et déclenchera le contrat de prédicat correspondant pour déverrouiller les jetons déposés par l’utilisateur.
Les données d’entrée de preuve de preuve transmises à la fonction incluent les données suivantes :
headerNumber : contient l’en-tête du bloc de point de contrôle pour la transaction de retrait
blockProof : Prouver que l’en-tête de bloc dans la chaîne enfant est le nœud feuille de la racine de merkle qui s’est engagé
blockNumber : le numéro de bloc sur la chaîne enfant qui contient la transaction de retrait
blockTime : l’horodatage de bloc de la transaction de retrait
txRoot : La valeur racine de l’arborescence des transactions de bloc
receiptRoot : valeur racine de l’arborescence des reçus de bloc
Reçu : un reçu pour une transaction de retrait
receiptProof : preuve Merck du reçu de la transaction de retrait
branchMask : chemin d’accès à la réception représentée par les 32 bits de l’arborescence de réception
receiptLogIndex : l’index du journal qui est lu à partir de l’arborescence des reçus
Voici la logique de base de la fonction, qui comprend principalement trois parties : la première partie consiste à vérifier la validité du reçu de la transaction de retrait, la deuxième partie consiste à vérifier si le point de contrôle contient le bloc de transaction, et la troisième partie consiste à appeler la fonction exitTokens() dans le contrat de prédicat pour envoyer les jetons verrouillés à l’utilisateur.
Prenons l’exemple du contrat ERC20Predicate, c’est-à-dire qu’après avoir décodé le récepteur, l’expéditeur et le nombre de jetons envoyés à partir du journal, un nombre donné de jetons sera envoyé à l’utilisateur.
Selon l’analyse du code source du processus de messagerie inter-chaînes PoS Bridge, les appels de fonction de l’ensemble du processus ne peuvent être appelés que par le rôle spécifié par le validateur, de sorte que la sécurité du cross-chain n’est garantie que par le PoS (notaire).
Messagerie 4_Cross-chain—Plasma Bridge
Plasma Bridge comprend également deux fonctions : Dépôt et Retraits, comme illustré dans la figure suivante :
Polygon Plasma est légèrement différent de l’implémentation Bitcoin Plasma MVP présentée dans le premier article de notre série de ponts inter-chaînes, qui utilise principalement le modèle Plasma MoreVP basé sur les comptes. Par rapport à Plasma, l’algorithme a été partiellement amélioré dans la partie retrait.
Étant donné que le transfert de tokens de l’ERC20 et de l’ERC721 est réalisé par le biais d’un journal d’événements similaire à un UTXO Bitcoin, introduisons d’abord l’événement :
input1 : Le solde du compte de l’expéditeur avant le virement
input2 : Le solde du compte du destinataire avant le virement
output1 : Le solde du compte de l’expéditeur après le transfert
output2 : Le solde du compte du destinataire après le virement
Deuxièmement, étant donné que le MVP Plasma d’origine est généré par un seul opérateur ou un petit nombre de producteurs de blocs, il existe deux scénarios d’attaque sur Polygon :
Opérateur maléfique :
Article précédent (Recherche sur la sécurité des ponts inter-chaînes (2) | Nomad cross-chain bridge) mentionne que lorsque la transaction d’un utilisateur est empaquetée dans un bloc Plasma par l’opérateur, il y a une indisponibilité des données hors chaîne. Par conséquent, lorsqu’un utilisateur effectue une transaction de sortie, s’il commence à se retirer d’une transaction plus ancienne, l’opérateur peut la contester avec l’une de ses transactions les plus récentes, et la contestation sera couronnée de succès. Dans le même temps, en raison du mécanisme de point de contrôle PoS utilisé dans Plasma, si l’opérateur est de connivence avec les validateurs pour faire le mal, ils peuvent même forger des transitions d’état et les soumettre à Ethereum.
Utilisateur maléfique :
Les utilisateurs continuent de dépenser des tokens sur Polygon après avoir initié une transaction de sortie, similaire à une double dépense inter-chaînes.
En résumé, l’algorithme Plasma MoreVp de Polygon utilise un autre algorithme pour calculer la priorité de sortie, qui consiste à sortir de la transaction la plus récente. Étant donné que cette méthode utilise un événement LogTransfer similaire à UTXO, tant que la transaction légitime de l’utilisateur utilise les entrées1 et input2 correctes, même si certaines transactions malveillantes sont empaquetées avant la transaction de l’utilisateur, la transaction de l’utilisateur peut être gérée correctement, car la transaction de l’utilisateur provient uniquement d’une entrée valide. Le pseudo-code pertinent est le suivant :
Dépôt
Prenons l’exemple de l’utilisateur Alice qui utilise Plasma Bridge pour envoyer des tokens de son compte Ethereum à son compte Polygon :
Tout d’abord, les utilisateurs doivent également autoriser les actifs de jetons qu’ils doivent transférer au depositManager du contrat Polygon sur la chaîne principale (Ethereum) via la fonction approtect.
Une fois la transaction autorisée confirmée, l’utilisateur appelle la fonction erc20token.deposit() pour déclencher la fonction depositERC20ForUser() du contrat depositManager et déposer les actifs de tokens ERC20 de l’utilisateur.
Lorsque le réseau principal Ethereum confirme la transaction de dépôt, il crée un bloc contenant uniquement cette transaction et l’envoie au contrat childChain sur le réseau Polygon en utilisant le mécanisme de synchronisation d’état, frappe la même quantité de pièces mappées et les dépose sur le compte de l’utilisateur sur Polygon.
Remarque : Selon l’analyse du code source du contrat childChain, Plasma ne prend en charge que trois types, à savoir : ETH, ERC20 et ERC721.
Retrait
Lorsqu’un utilisateur souhaite utiliser le pont Plasma pour retirer des actifs de Polygon vers Ethereum, il doit suivre les étapes suivantes :
L’utilisateur brûle les actifs de tokens mappés sur la chaîne Polygon en appelant la fonction withdraw() de la pièce mappée sur Polygon :
Vous pouvez également appeler l’implémentation de l’interface withdrawStart() du client Plasma sur Polygon.
L’utilisateur peut appeler la fonction startExitWithBurntTokens() dans le contrat ERC20Predicate, qui appellera d’abord WithdrawManager.verifyInclusion() pour vérifier si le point de contrôle contient la transaction de retrait et le reçu correspondant, le code est le suivant :
Une fois la vérification passée, WithdrawManager.addExitToQueue() sera appelée pour l’insérer dans la file d’attente des messages par ordre de priorité :
Enfin, addExitToQueue() appelle _addExitToQueue() pour frapper un NFT en tant que bon de remboursement :
L’utilisateur attend une période de défi de 7 jours
Une fois la période de défi terminée, vous pouvez appeler la fonction WithdrawManager.processExits() pour envoyer des jetons à l’utilisateur.
La fonction est divisée en deux étapes : tout d’abord, vérifiez si la transaction de retrait dans la file d’attente de messages a dépassé la période de contestation de 7 jours et, si la période de contestation est passée, supprimez la transaction de la file d’attente :
Ensuite, déterminez si le NFT du bon de remboursement a été supprimé pendant la période de défi, et si ce n’est pas le cas, le NFT sera détruit et les actifs correspondants seront retournés à l’utilisateur :
5_Polygon Vulnérabilité de double dépense de Plasma Bridge
Le 5 octobre 2021, le White Hat Gerhard Wagner a déposé une vulnérabilité de Polygon qui pourrait conduire à une attaque à double dépense impliquant 850 millions de dollars, pour laquelle le White Hat a reçu un bug bounty officiel de 2 000 000 $ de la part de Polygon.
Dans l’introduction de Plasma Bridge ci-dessus, nous savons que le processus complet de transaction de retrait est le suivant :
- L’utilisateur initie une transaction de retrait sur Polygon, ce qui brûle les jetons de l’utilisateur sur Polygon ;
- Après un intervalle de point de contrôle (environ 30 minutes), attendez que la transaction de retrait soit incluse dans le point de contrôle ;
- Plus des 2/3 des validateurs le signent et le soumettent à Ethereum, auquel cas l’utilisateur appelle startExitWithBurntTokens() dans le contrat ERC20PredicateBurnOnly pour vérifier si le point de contrôle contient des transactions de gravure ;
- Si la vérification est réussie, un bon de remboursement NFT sera frappé et envoyé à l’utilisateur
- Les utilisateurs attendent une période de défi de 7 jours
- Appelez WithdrawManager.processExits() pour détruire le NFT et rembourser l’utilisateur
Remarque : Afin d’éviter la relecture des transactions (attaques de double dépense), Polygon utilise les NFT comme preuve de remboursement pour identifier de manière unique une transaction de retrait. Cependant, en raison du défaut de génération d’ID NFT, les attaquants peuvent construire des paramètres pour générer plusieurs NFT avec des ID différents en utilisant la même transaction de retrait valide, puis utiliser ces NFT pour les transactions de remboursement, réalisant ainsi une « attaque à double dépense ».
Voici un aperçu de la façon de générer des NFT :
D’après l’analyse du code source ci-dessus, on peut voir que addExitToQueue() appellera _addExitToQueue() pour frapper un NFT :
Selon l’analyse des paramètres, exitid = priorité, alors l’ID du NFT est généré par le décalage vers la gauche de la priorité d’âge dans Plasma Bridge.
Comme on peut le voir dans l’analyse du code source ci-dessus, l’âge est la valeur de retour de la fonction WithdrawManager.verifyInclusion(), qui vérifiera d’abord la validité de la transaction de retrait, puis générera l’âge correspondant si la vérification est réussie. Dans la logique de validation, la valeur décodée par les données du paramètre contrôlable est branchMaskBytes :
Cette valeur est également utilisée lors de la génération de l’âge :
Tracez la fonction MerklePatriciaProof.verify() appelée dans la logique de vérification des transactions et constatez que la fonction appelle _getNibbleArray() pour transcoder branchMaskBytes :
Continuez à suivre la fonction de décodage, qui supprime une partie de la valeur lors du transcodage branchMaskBytes, et cette façon de perdre la valeur entraînera le transcodage de différentes valeurs pour obtenir la même valeur décodée. Plus précisément, si le premier bit hexadécimal (un demi-octet) de la valeur entrante codée en hp b est 1 ou 3, le deuxième bit hexadécimal est analysé. Dans le cas contraire, le premier octet est tout simplement ignoré.
Si un attaquant construit un paramètre branchMaskBytes de sorte que le premier bit hexadécimal ne soit pas égal à 1 et 3, il y a 14*16 = 224 façons d’obtenir la même valeur transcodée.
Le processus d’attaque spécifique est le suivant :
- Déposez une grande quantité d’ETH/tokens sur Polygon via Polygon Plasma
- Initier une transaction de retrait sur Polygon et attendre une période de défi de 7 jours
- Modifiez le premier octet du paramètre branchMaskBytes dans la transaction de retrait (la même transaction valide peut être soumise à nouveau jusqu’à 223 fois) et lancez la transaction de retrait à plusieurs reprises
En résumé, cette vulnérabilité est principalement due à un problème dans la conception de l’algorithme d’identification du NFT qui génère le bon de remboursement pour empêcher la relecture, ce qui fait qu’une même transaction de remboursement peut générer différents NFT, entraînant une attaque de double dépense. Il s’avère que le premier octet du masque de branche encodé doit toujours être 0x00. La solution consiste à vérifier si le premier octet du masque de branche encodé est 0x00 et à ne pas le traiter comme un masque incorrect.
Eh bien, le partage d’aujourd’hui est terminé, dans le prochain numéro, l’équipe de recherche sur la sécurité de la sécurité de la chaîne de Chengdu présentera la recherche sur la sécurité d’un autre projet inter-chaînes, attendez-le avec impatience.
Voir l'original
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.
Dialyse de sécurité Polygon Warrior : comment assurer la sécurité et la stabilité inter-chaînes ?
Auteur : Chengdu Lian’an
Original : « Recherche sur la sécurité des ponts inter-chaînes (3) » | Dialyse sécurisée Polygon Warrior, comment empêcher l’ouverture de la « boîte de Pandore » ?
Bienvenue dans la série d’articles sur la « Recherche sur la sécurité des ponts inter-chaînes » produite par Chengdu Chain Security, dans l’article précédent (Recherche sur la sécurité des ponts inter-chaînes (2) | Qu’est-ce que le premier braquage décentralisé du pont Nomad nous apporte ?), nous effectuons une analyse technique professionnelle du protocole du pont Nomad en détail.
Aujourd’hui, l’équipe de recherche sur la sécurité de la chaîne de Chengdu effectuera une dialyse en toute sécurité de Polygon, un guerrier polygonal, alors continuez à lire.
Qui est 1_Polygon ?
Polygon est la solution de mise à l’échelle de couche 2 d’Ethereum, dont la vision est de construire l’Internet blockchain d’Ethereum. Polygon fournit un cadre commun qui permet aux développeurs de tirer parti de la sécurité d’Ethereum pour créer des chaînes personnalisées et axées sur les applications et fournir un réseau interopérable qui combine une variété de schémas de mise à l’échelle différents tels que : zk-rollup, PoS, etc. Parmi eux, Polygon PoS est actuellement la solution de mise à l’échelle la plus mature et la plus connue sur Polygon. Il utilise la sidechain pour le traitement des transactions afin d’améliorer la vitesse des transactions et d’économiser la consommation de gaz, et la structure du réseau comprend principalement les trois couches suivantes :
Couche Ethereum :
Une série de contrats sur le réseau principal Ethereum, comprenant principalement : les contrats de jalonnement, de point de contrôle et de récompense, sont responsables des fonctions de gestion du jalonnement liées aux mises PoS, notamment : fournir la fonction de jalonnement du jeton natif MATIC, afin que toute personne qui jalonne le jeton puisse rejoindre le système en tant que validateur, vérifier la conversion du réseau Polygon pour obtenir des récompenses de jalonnement, punir les validateurs pour les doubles signatures, les temps d’arrêt des validateurs et autres comportements illégaux, et sauver les points de contrôle.
Couche de Heimdall :
La couche de validation de preuve d’enjeu, qui se compose d’un ensemble de nœuds PoS Heimdall, est chargée de soumettre les points de contrôle du réseau Polygon au réseau principal Ethereum tout en écoutant un ensemble de contrats de jalonnement déployés sur Ethereum. Le processus principal est le suivant : tout d’abord, sélectionnez un sous-ensemble de validateurs actifs dans le pool de validateurs en tant que producteurs de blocs, qui seront responsables de la création de blocs au niveau de la couche Bor et de leur diffusion, puis validez le hachage racine de Merkle et ajoutez des signatures en fonction des points de contrôle soumis par Bor, et enfin, le proposant sera responsable de la collecte de toutes les signatures de validateurs pour le point de contrôle spécifié, et si le nombre de signatures atteint plus de 2/3, le point de contrôle sera soumis sur Ethereum.
Bor Layer :
La couche de producteur de blocs, qui se compose d’un groupe de producteurs de blocs régulièrement sélectionnés par un comité de validateurs sur la couche de Heimdall, est un sous-ensemble de validateurs responsables de l’agrégation des transactions sur la sidechain Polygon et de la génération de blocs. Cette couche publie périodiquement des points de contrôle dans la couche de Heimdall, où le point de contrôle représente un instantané de la chaîne de Bor, comme illustré dans l’image ci-dessous.
2_Polygon Interopérabilité
2.1 Point de contrôle
Le mécanisme de point de contrôle est un mécanisme permettant de synchroniser les données de la couche Bor avec Ethereum, où les données synchronisées sont un point de contrôle, c’est-à-dire un instantané des données de bloc de la couche Bor contenues dans un intervalle de point de contrôle, le code source est le suivant :
Proposant : Le soumissionnaire, qui est également sélectionné par les validateurs, les producteurs de blocs et les proposants sont un sous-ensemble de validateurs, et leurs responsabilités sont déterminées par leur participation dans le pool global
RootHash : est un hachage de Merkle généré à partir du bloc Bor entre StartBlock et EndBlock
Ce qui suit est un pseudo-code pour le bloc Bor numéroté de 1 à n pour générer la valeur RootHash :
En résumé, cette valeur est la valeur de hachage racine de l’arbre de Merkel, qui est composée du numéro de bloc dans l’en-tête de bloc Bor, de l’horodatage du bloc, de la valeur de hachage racine de l’arborescence des transactions tx hash et de la valeur de hachage keccak256 calculée à partir du hachage racine de l’arbre de réception.
AccountRootHash : un hachage Merkle des informations de compte liées au validateur qui doit être envoyé à chaque point de contrôle sur Ethereum, et la valeur de hachage des informations de compte individuelles est calculée comme suit :
Le AccountRootHash est généré à partir du hachage racine de l’arborescence Merkle du compte de la même manière que la valeur RootHash.
2.2 StateSync
StateSync fait référence à la synchronisation des données Ethereum avec la chaîne Polygon Matic, qui est principalement divisée en étapes suivantes :
Tout d’abord, le contrat sur Ethereum déclenchera la fonction syncState() dans StateSender.sol pour la synchronisation de l’état
La fonction syncState() émettra un événement d’événement comme suit :
Tous les validateurs de la couche Heimdall recevront l’événement, et l’un des validateurs empaquetera la transaction dans le bloc heimdall et l’ajoutera à la liste de synchronisation d’état en attente ;
Le nœud de la couche bor obtiendra la liste ci-dessus à synchroniser via l’API et la remettra au contrat de la couche bor pour un traitement ultérieur de la logique métier.
2.3 Pont polygonal
Polygon Bridge permet un canal inter-chaînes bidirectionnel entre Polygon et Ethereum, ce qui permet aux utilisateurs de transférer plus facilement des tokens entre deux plateformes de chaînes différentes sans menaces tierces ni contraintes de liquidité du marché. Il existe deux types de Polygon Bridge, PoS et Plasma, et les deux présentent les similitudes suivantes dans le transfert d’actifs entre Polygon et Ethereum :
a : Tous les actifs de tokens transférés d’Ethereum seront d’abord verrouillés sur Ethereum, et le même nombre de tokens mappés sera frappé sur Polygon ;
b : Afin de retirer des actifs de tokens sur Ethereum, vous devez d’abord brûler ces tokens mappés sur Polygon, puis déverrouiller les actifs verrouillés sur Ethereum ;
La figure suivante montre la comparaison entre PoS Bridge et Plasma Bridge :
Comme on peut le voir sur la figure ci-dessus, en termes de sécurité, PoS Bridge s’appuie sur la sécurité de l’ensemble de validateurs externes, tandis que Plasma s’appuie sur la sécurité de la chaîne principale Ethereum. Dans le même temps, lorsque les utilisateurs effectuent des transferts d’actifs inter-chaînes (tels que le transfert de tokens de Polygon vers Ethereum), le PoS ne nécessite qu’un intervalle de point de contrôle, d’environ 20 minutes à 3 heures, tandis que Plasma nécessite une période de contestation de 7 jours. Dans le même temps, PoS prend en charge davantage de jetons standard, tandis que Plasma ne prend en charge que trois types, à savoir : ETH, ERC20, ERC721.
Messagerie à 3 _Cross chaînes—PoS Bridge
Le pont PoS se compose principalement de deux fonctions : le dépôt fait référence au transfert des actifs des utilisateurs sur Ethereum vers Polygon, et les retraits font référence au retrait d’actifs de Polygon vers Ethereum.
Dépôt
Voici un exemple d’utilisateur Alice utilisant PoS Bridge pour envoyer des actifs symboliques de son compte Ethereum à son compte Polygon :
La fonction approtect a deux paramètres :
Spender : l’adresse de destination où l’utilisateur autorise la dépense de tokens
amount : Le nombre de jetons qui peuvent être dépensés
La fonction depositFor comporte trois paramètres :
user : L’adresse de l’utilisateur qui a reçu les tokens de dépôt sur Polygon
rootToken : L’adresse du jeton sur la chaîne principale d’Ethereum
depositData : Le nombre de tokens encodés par ABI
Voici le code spécifique de la fonction depositFor dans le contrat RootChainManager :
En analysant le code source, on peut voir que la fonction obtient d’abord l’adresse de contrat prédicat correspondant au jeton, puis appelle sa fonction lockTokens() pour verrouiller le jeton dans le contrat. Enfin, syncState() sera appelé pour la synchronisation d’état par _stateSender, qui ne peut être appelé que par l’expéditeur d’état défini par admin.
Le premier paramètre est l’index du numéro de séquence du journal, le deuxième paramètre est utilisé pour vérifier si l’appelant est une adresse de contrat légitime enregistrée et le troisième paramètre est les données qui doivent être synchronisées avec l’état. La transaction est ajoutée au bloc Heimdall et à la liste de synchronisation des états en attente.
data : bytes32 syncType et bytes syncData. Lorsque syncType est mappé, syncData est l’adresse rootToken codée, l’adresse childToken et bytes32 tokenType, et lorsque syncType est deposit, syncData est l’adresse utilisateur codée. L’adresse rootToken et depositData du type bytes. depositData est la quantité dans REC20 et tokenId dans ERC721.
La fonction a deux paramètres :
user : L’adresse de l’utilisateur qui effectue un dépôt
depositData : Le montant encodé en ABI
Retraits
Voici un exemple d’utilisateur Alice utilisant PoS Bridge pour retirer les fonds déposés sur son compte Polygon vers son compte Ethereum :
Retirer ne contient qu’un seul paramètre : le nombre de jetons qui seront brûlés. La fonction withdraw() dans le contrat de token correspondant est la suivante :
Les transactions ci-dessus seront incluses dans le point de contrôle après environ 20 minutes à 3 heures, et le validateur les soumettra à Ethereum.
Une fois la transaction ajoutée au point de contrôle et soumise à Ethereum, la fonction exit() du contrat RootChainManager sur Ethereum sera appelée, ce qui confirmera la validité de la transaction de retrait sur Polygon en vérifiant le contenu du point de contrôle soumis, et déclenchera le contrat de prédicat correspondant pour déverrouiller les jetons déposés par l’utilisateur.
Les données d’entrée de preuve de preuve transmises à la fonction incluent les données suivantes :
headerNumber : contient l’en-tête du bloc de point de contrôle pour la transaction de retrait
blockProof : Prouver que l’en-tête de bloc dans la chaîne enfant est le nœud feuille de la racine de merkle qui s’est engagé
blockNumber : le numéro de bloc sur la chaîne enfant qui contient la transaction de retrait
blockTime : l’horodatage de bloc de la transaction de retrait
txRoot : La valeur racine de l’arborescence des transactions de bloc
receiptRoot : valeur racine de l’arborescence des reçus de bloc
Reçu : un reçu pour une transaction de retrait
receiptProof : preuve Merck du reçu de la transaction de retrait
branchMask : chemin d’accès à la réception représentée par les 32 bits de l’arborescence de réception
Voici la logique de base de la fonction, qui comprend principalement trois parties : la première partie consiste à vérifier la validité du reçu de la transaction de retrait, la deuxième partie consiste à vérifier si le point de contrôle contient le bloc de transaction, et la troisième partie consiste à appeler la fonction exitTokens() dans le contrat de prédicat pour envoyer les jetons verrouillés à l’utilisateur.
Selon l’analyse du code source du processus de messagerie inter-chaînes PoS Bridge, les appels de fonction de l’ensemble du processus ne peuvent être appelés que par le rôle spécifié par le validateur, de sorte que la sécurité du cross-chain n’est garantie que par le PoS (notaire).
Messagerie 4_Cross-chain—Plasma Bridge
Plasma Bridge comprend également deux fonctions : Dépôt et Retraits, comme illustré dans la figure suivante :
Polygon Plasma est légèrement différent de l’implémentation Bitcoin Plasma MVP présentée dans le premier article de notre série de ponts inter-chaînes, qui utilise principalement le modèle Plasma MoreVP basé sur les comptes. Par rapport à Plasma, l’algorithme a été partiellement amélioré dans la partie retrait.
Étant donné que le transfert de tokens de l’ERC20 et de l’ERC721 est réalisé par le biais d’un journal d’événements similaire à un UTXO Bitcoin, introduisons d’abord l’événement :
input1 : Le solde du compte de l’expéditeur avant le virement
input2 : Le solde du compte du destinataire avant le virement
output1 : Le solde du compte de l’expéditeur après le transfert
output2 : Le solde du compte du destinataire après le virement
Deuxièmement, étant donné que le MVP Plasma d’origine est généré par un seul opérateur ou un petit nombre de producteurs de blocs, il existe deux scénarios d’attaque sur Polygon :
Opérateur maléfique :
Article précédent (Recherche sur la sécurité des ponts inter-chaînes (2) | Nomad cross-chain bridge) mentionne que lorsque la transaction d’un utilisateur est empaquetée dans un bloc Plasma par l’opérateur, il y a une indisponibilité des données hors chaîne. Par conséquent, lorsqu’un utilisateur effectue une transaction de sortie, s’il commence à se retirer d’une transaction plus ancienne, l’opérateur peut la contester avec l’une de ses transactions les plus récentes, et la contestation sera couronnée de succès. Dans le même temps, en raison du mécanisme de point de contrôle PoS utilisé dans Plasma, si l’opérateur est de connivence avec les validateurs pour faire le mal, ils peuvent même forger des transitions d’état et les soumettre à Ethereum.
Utilisateur maléfique :
Les utilisateurs continuent de dépenser des tokens sur Polygon après avoir initié une transaction de sortie, similaire à une double dépense inter-chaînes.
En résumé, l’algorithme Plasma MoreVp de Polygon utilise un autre algorithme pour calculer la priorité de sortie, qui consiste à sortir de la transaction la plus récente. Étant donné que cette méthode utilise un événement LogTransfer similaire à UTXO, tant que la transaction légitime de l’utilisateur utilise les entrées1 et input2 correctes, même si certaines transactions malveillantes sont empaquetées avant la transaction de l’utilisateur, la transaction de l’utilisateur peut être gérée correctement, car la transaction de l’utilisateur provient uniquement d’une entrée valide. Le pseudo-code pertinent est le suivant :
Dépôt
Prenons l’exemple de l’utilisateur Alice qui utilise Plasma Bridge pour envoyer des tokens de son compte Ethereum à son compte Polygon :
Tout d’abord, les utilisateurs doivent également autoriser les actifs de jetons qu’ils doivent transférer au depositManager du contrat Polygon sur la chaîne principale (Ethereum) via la fonction approtect.
Une fois la transaction autorisée confirmée, l’utilisateur appelle la fonction erc20token.deposit() pour déclencher la fonction depositERC20ForUser() du contrat depositManager et déposer les actifs de tokens ERC20 de l’utilisateur.
Remarque : Selon l’analyse du code source du contrat childChain, Plasma ne prend en charge que trois types, à savoir : ETH, ERC20 et ERC721.
Retrait
Lorsqu’un utilisateur souhaite utiliser le pont Plasma pour retirer des actifs de Polygon vers Ethereum, il doit suivre les étapes suivantes :
Vous pouvez également appeler l’implémentation de l’interface withdrawStart() du client Plasma sur Polygon.
Une fois la vérification passée, WithdrawManager.addExitToQueue() sera appelée pour l’insérer dans la file d’attente des messages par ordre de priorité :
Enfin, addExitToQueue() appelle _addExitToQueue() pour frapper un NFT en tant que bon de remboursement :
L’utilisateur attend une période de défi de 7 jours
Une fois la période de défi terminée, vous pouvez appeler la fonction WithdrawManager.processExits() pour envoyer des jetons à l’utilisateur.
La fonction est divisée en deux étapes : tout d’abord, vérifiez si la transaction de retrait dans la file d’attente de messages a dépassé la période de contestation de 7 jours et, si la période de contestation est passée, supprimez la transaction de la file d’attente :
Ensuite, déterminez si le NFT du bon de remboursement a été supprimé pendant la période de défi, et si ce n’est pas le cas, le NFT sera détruit et les actifs correspondants seront retournés à l’utilisateur :
5_Polygon Vulnérabilité de double dépense de Plasma Bridge
Le 5 octobre 2021, le White Hat Gerhard Wagner a déposé une vulnérabilité de Polygon qui pourrait conduire à une attaque à double dépense impliquant 850 millions de dollars, pour laquelle le White Hat a reçu un bug bounty officiel de 2 000 000 $ de la part de Polygon.
Dans l’introduction de Plasma Bridge ci-dessus, nous savons que le processus complet de transaction de retrait est le suivant :
- L’utilisateur initie une transaction de retrait sur Polygon, ce qui brûle les jetons de l’utilisateur sur Polygon ;
- Après un intervalle de point de contrôle (environ 30 minutes), attendez que la transaction de retrait soit incluse dans le point de contrôle ;
- Plus des 2/3 des validateurs le signent et le soumettent à Ethereum, auquel cas l’utilisateur appelle startExitWithBurntTokens() dans le contrat ERC20PredicateBurnOnly pour vérifier si le point de contrôle contient des transactions de gravure ;
- Si la vérification est réussie, un bon de remboursement NFT sera frappé et envoyé à l’utilisateur
- Les utilisateurs attendent une période de défi de 7 jours
- Appelez WithdrawManager.processExits() pour détruire le NFT et rembourser l’utilisateur
Remarque : Afin d’éviter la relecture des transactions (attaques de double dépense), Polygon utilise les NFT comme preuve de remboursement pour identifier de manière unique une transaction de retrait. Cependant, en raison du défaut de génération d’ID NFT, les attaquants peuvent construire des paramètres pour générer plusieurs NFT avec des ID différents en utilisant la même transaction de retrait valide, puis utiliser ces NFT pour les transactions de remboursement, réalisant ainsi une « attaque à double dépense ».
Voici un aperçu de la façon de générer des NFT :
Selon l’analyse des paramètres, exitid = priorité, alors l’ID du NFT est généré par le décalage vers la gauche de la priorité d’âge dans Plasma Bridge.
Cette valeur est également utilisée lors de la génération de l’âge :
Si un attaquant construit un paramètre branchMaskBytes de sorte que le premier bit hexadécimal ne soit pas égal à 1 et 3, il y a 14*16 = 224 façons d’obtenir la même valeur transcodée.
Le processus d’attaque spécifique est le suivant :
- Déposez une grande quantité d’ETH/tokens sur Polygon via Polygon Plasma
- Initier une transaction de retrait sur Polygon et attendre une période de défi de 7 jours
- Modifiez le premier octet du paramètre branchMaskBytes dans la transaction de retrait (la même transaction valide peut être soumise à nouveau jusqu’à 223 fois) et lancez la transaction de retrait à plusieurs reprises
En résumé, cette vulnérabilité est principalement due à un problème dans la conception de l’algorithme d’identification du NFT qui génère le bon de remboursement pour empêcher la relecture, ce qui fait qu’une même transaction de remboursement peut générer différents NFT, entraînant une attaque de double dépense. Il s’avère que le premier octet du masque de branche encodé doit toujours être 0x00. La solution consiste à vérifier si le premier octet du masque de branche encodé est 0x00 et à ne pas le traiter comme un masque incorrect.
Eh bien, le partage d’aujourd’hui est terminé, dans le prochain numéro, l’équipe de recherche sur la sécurité de la sécurité de la chaîne de Chengdu présentera la recherche sur la sécurité d’un autre projet inter-chaînes, attendez-le avec impatience.