Briser l'immuabilité de la blockchain : comment le modèle de proxy peut réaliser des mises à niveau de contrats intelligents

Le modèle de proxy permet aux contrats intelligents de mettre à niveau leur logique tout en conservant leurs adresses en chaîne et leurs valeurs d'état. L'appel au contrat de proxy exécutera le code du contrat logique via l'appel de délégué pour modifier l'état du contrat de proxy.

Cet article fournit une vue d'ensemble des types de contrats de proxy, des incidents de sécurité et des recommandations connexes, ainsi que des meilleures pratiques d'utilisation des contrats de proxy.

Introduction aux contrats évolutifs et au mode proxy

Nous connaissons tous la fonctionnalité "inviolable" de la blockchain, et le code de contrat intelligent ne peut pas être modifié après avoir été déployé sur la blockchain.

Ainsi, lorsque les développeurs souhaitent mettre à jour le code du contrat pour des mises à niveau logiques, des corrections de bogues ou des mises à jour de sécurité, ils doivent déployer un nouveau contrat et une nouvelle adresse de contrat sera générée.

Pour résoudre ce problème, vous pouvez utiliser le mode proxy.

Le mode proxy réalise l'évolutivité du contrat sans modifier l'adresse de déploiement du contrat, qui est actuellement le mode de mise à niveau de contrat le plus courant.

Le mode proxy est un système de contrat évolutif, comprenant un contrat de proxy et un contrat d'implémentation logique.

Le contrat de proxy gère l'interaction de l'utilisateur et le stockage de l'état des données et du contrat. L'appel de l'utilisateur au contrat de proxy exécutera le code du contrat logique via la fonction delegatecall(), modifiant ainsi l'état du contrat de proxy. La mise à niveau est réalisée en mettant à jour l'adresse de contrat logique enregistrée dans l'emplacement de stockage prédéterminé du contrat proxy.

Les trois modes de proxy les plus conventionnels sont le proxy transparent, le proxy UUPS et le proxy Beacon.

Procuration transparente

En mode proxy transparent, la fonction de mise à niveau est implémentée dans le contrat de proxy. Le rôle d'administrateur du contrat de proxy reçoit l'autorité directe d'exploiter le contrat de proxy pour mettre à jour l'adresse de mise en œuvre logique correspondant au proxy. Les appelants sans privilèges d'administrateur délégueront leurs appels au contrat de mise en œuvre.

Remarque : L'administrateur du contrat de proxy ne peut pas jouer un rôle clé dans la mise en œuvre logique du contrat, ni même être un utilisateur ordinaire, car l'administrateur de proxy ne peut pas interagir avec le contrat de mise en œuvre.

** Proxy UUPS **

En mode UUPS (Universal Upgradeable Proxy Standard), la fonction de mise à jour du contrat est implémentée dans le contrat logique. Étant donné que le mécanisme de mise à niveau est stocké dans le contrat logique, la version mise à niveau peut supprimer la logique liée à la mise à niveau pour interdire les mises à niveau futures. Dans ce mode, tous les appels au contrat de proxy sont transmis au contrat d'implémentation logique.

** Proxy balise **

Le mode proxy Beacon permet à plusieurs contrats proxy de partager la même implémentation logique en référençant le contrat Beacon. Le contrat Beacon fournit l'adresse du contrat d'implémentation logique pour le contrat proxy appelé. Lors de la mise à niveau vers une nouvelle adresse d'implémentation logique, seule l'adresse enregistrée dans le contrat Beacon doit être mise à jour.

Utilisation abusive du proxy et incidents de sécurité

Les développeurs peuvent utiliser des contrats en mode proxy pour implémenter des systèmes de contrats évolutifs. Cependant, le mode proxy a également certains seuils opérationnels et, s'il est mal utilisé, il peut entraîner des problèmes de sécurité dévastateurs pour le projet. Les sections suivantes présentent les incidents liés à l'utilisation abusive des proxys et les risques de centralisation que les proxys posent.

** Divulgation de clé gérée par proxy **

L'administrateur proxy contrôle le mécanisme de mise à niveau du mode proxy transparent. Si la clé privée de l'administrateur est divulguée, l'attaquant peut mettre à niveau le contrat logique et exécuter sa propre logique malveillante sur l'état du proxy.

Le 5 mars 2021, PAID Network a subi une attaque "minting" causée par une mauvaise gestion des clés privées. Le réseau PAID a été exploité par un attaquant qui a volé la clé privée de l'administrateur proxy et déclenché un mécanisme de mise à niveau pour modifier le contrat logique.

Après la mise à niveau, l'attaquant peut détruire le PAID de l'utilisateur et créer un lot de PAID pour lui-même, qui pourra être vendu plus tard. Il n'y a pas de vulnérabilité de sécurité dans le code lui-même, mais l'attaquant a obtenu la clé privée pour mettre à jour le contrat auprès de l'administrateur.

** Implémentation de proxy UUPS non initialisée **

Pour le mode proxy UUPS, lors de l'initialisation du contrat de proxy, les paramètres initiaux sont transmis au contrat de proxy par l'appelant, puis le contrat de proxy appelle la fonction initialize() dans le contrat logique pour réaliser l'initialisation.

La fonction initialize() est généralement protégée par le modificateur "initializer" pour limiter la fonction à n'être appelée qu'une seule fois. Après avoir appelé la fonction initialize(), du point de vue du contrat de proxy, le contrat logique est initialisé.

Cependant, du point de vue du contrat logique, le contrat logique n'est pas initialisé car initialize() n'est pas appelé directement dans le contrat logique. Étant donné que le contrat logique lui-même n'est pas initialisé, n'importe qui peut appeler la fonction initialize() pour l'initialiser, définir la variable d'état sur une valeur malveillante et potentiellement reprendre le contrat logique.

L'impact de la reprise d'un contrat logique dépend du code de contrat dans le système. Dans le pire des cas, un attaquant peut mettre à niveau le contrat logique en mode proxy UUPS vers un contrat malveillant et exécuter un appel de fonction "d'autodestruction", ce qui peut rendre l'ensemble du contrat proxy inutile, et les actifs du contrat seront être définitivement détruit perdu.

cas

① Parity Multisig Freeze : Le contrat logique n'est pas initialisé. L'attaquant déclenche l'initialisation de nombreux portefeuilles et verrouille l'ether dans le contrat en appelant selfdestruction().

② Harvest Finance, Teller, KeeperDAO et Rivermen utilisent tous des contrats logiques non initialisés, ce qui permettra aux attaquants de définir arbitrairement les paramètres d'initialisation des contrats et d'exécuter selfdestruction() pendant l'appel délégué() pour détruire le contrat de proxy.

Conflit de stockage

Dans un système de contrat évolutif, le contrat de proxy ne déclare pas de variables d'état, mais utilise des emplacements de stockage pseudo-aléatoires pour stocker des données importantes.

Les contrats proxy stockent les valeurs des variables d'état de contrat logique par rapport à l'endroit où elles ont été déclarées. Si le contrat de proxy déclare ses propres variables d'état et que le proxy et le contrat logique essaient d'utiliser le même emplacement de stockage, un conflit de stockage se produit.

Le contrat de proxy fourni par la bibliothèque OpenZeppelin ne déclare pas de variables d'état dans le contrat, mais basé sur la norme EIP 1967, enregistre la valeur qui doit être stockée (telle que l'adresse de gestion) dans un emplacement de stockage spécifique pour éviter les conflits.

cas

Le 23 juillet 2022, heure de Pékin, la plate-forme musicale décentralisée Audius a été piratée. L'incident a été causé par l'introduction d'une nouvelle logique dans le contrat de procuration, entraînant des conflits de stockage.

Le contrat de proxy déclare une variable d'état d'adresse proxyAdmin et sa valeur sera lue de manière incorrecte lors de l'exécution du code de contrat logique.

La valeur de proxyAdmin personnalisée par la partie projet était considérée à tort comme la valeur de initialized et initialized, de sorte que le modificateur initializer renvoyait un mauvais résultat, ce qui permettait à l'attaquant d'appeler à nouveau la fonction initialize() et de s'octroyer le droit de gérer le contracter. Les attaquants ont ensuite modifié les paramètres de vote et adopté leur proposition malveillante afin de voler les actifs d'Audius.

Appeler un déléguéappel() dans un contrat logique ou un contrat non approuvé

Supposons que elephantcall() existe dans un contrat logique et que le contrat ne valide pas correctement la cible de l'appel. Dans ce cas, un attaquant peut exploiter cette fonction pour exécuter des appels à des contrats malveillants qu'il contrôle, pour subvertir les implémentations logiques ou pour exécuter une logique personnalisée.

De même, s'il existe une fonction address.call() sans restriction dans le contrat logique, une fois que l'attaquant a fourni de manière malveillante les champs d'adresse et de données, elle peut être utilisée comme contrat de proxy.

cas

Pickle Finance, Furucombo et attaques dYdX.

Dans ces incidents, le contrat vulnérable a été approuvé par le jeton utilisateur, et il y a un call()/delegatecall() dans le contrat qui est fourni par l'utilisateur pour appeler l'adresse et les données du contrat, l'attaquant pourra appeler le contrat de fonction transferFrom() pour retirer les soldes des utilisateurs. Lors de l'incident dYdX, dYdX a lancé sa propre attaque chapeau blanc pour protéger les fonds.

Les meilleures pratiques

en général

(1) Utilisez le mode proxy uniquement lorsque cela est nécessaire

Tous les contrats n'ont pas besoin d'être mis à jour. Comme indiqué ci-dessus, l'utilisation du modèle de proxy comporte de nombreux risques. La propriété "évolutive" soulève également des problèmes de confiance, car les administrateurs proxy peuvent mettre à jour les contrats sans le consentement de la communauté. Nous vous recommandons d'intégrer le modèle de proxy dans les projets uniquement lorsque cela est nécessaire.

(2) Ne modifiez pas la bibliothèque proxy

La bibliothèque de contrats de proxy est complexe, en particulier la partie qui traite de la gestion du stockage et des mécanismes de mise à niveau. Toute erreur dans la modification affectera le travail des contrats proxy et logique. Un grand nombre de bogues de haute gravité liés aux agents que nous avons trouvés lors de nos audits ont été causés par des modifications incorrectes de la bibliothèque d'agents. L'incident d'Audius est un excellent exemple des conséquences d'une modification abusive des contrats d'agence.

** Points clés du fonctionnement et de la gestion du contrat d'agence **

(1) Initialiser le contrat logique

Un attaquant peut prendre en charge un contrat logique non initialisé et potentiellement compromettre le système de contrat proxy. Veuillez donc initialiser le contrat logique après le déploiement ou utiliser _disableInitializers() dans le constructeur du contrat logique pour désactiver automatiquement l'initialisation.

(2) Assurer la sécurité du compte de gestion de l'agent

Un système de contrat évolutif nécessite généralement un rôle privilégié "d'administrateur proxy" pour gérer les mises à niveau de contrat. Si la clé de gestion est divulguée, l'attaquant peut librement mettre à niveau le contrat vers un contrat malveillant, qui peut voler les actifs des utilisateurs. Nous recommandons une gestion prudente des clés privées des comptes d'administration proxy pour éviter tout risque potentiel de piratage. Les portefeuilles multi-signatures peuvent être utilisés pour éviter les échecs de gestion des clés à un seul point.

(3) Utilisez un compte séparé pour une gestion transparente des proxys

La gestion des proxys et la gouvernance de la logique doivent être des adresses distinctes pour éviter toute perte d'interaction avec la mise en œuvre de la logique. Si la gestion du proxy et la gouvernance logique se réfèrent à la même adresse, aucun appel n'est renvoyé pour effectuer des fonctions privilégiées, interdisant ainsi les modifications des fonctions de gouvernance.

En relation avec le stockage des contrats proxy

(1) Soyez prudent lorsque vous déclarez des variables d'état dans des contrats de proxy

Comme expliqué dans le hack Audius, les contrats de proxy doivent être prudents lors de la déclaration de leurs propres variables d'état. Les variables d'état déclarées normalement dans les contrats de proxy peuvent provoquer des conflits de données lors de la lecture et de l'écriture de données. Si le contrat de proxy nécessite une variable d'état, enregistrez la valeur dans un emplacement de stockage comme EIP1967 pour éviter les conflits lors de l'exécution du code de contrat logique.

(2) Maintenir l'ordre de déclaration des variables et le type de contrat logique

Chaque version du contrat logique doit conserver le même ordre et le même type de variables d'état, et de nouvelles variables d'état doivent être ajoutées à la fin des variables existantes. Sinon, les appels délégués peuvent entraîner la lecture ou l'écrasement par les contrats de proxy de valeurs stockées incorrectes, et d'anciennes données peuvent être associées à des variables nouvellement déclarées, ce qui peut entraîner de graves problèmes pour les applications.

(3) Inclure les écarts de stockage dans le contrat de base

Les contrats logiques doivent inclure des espaces de stockage dans le code du contrat pour anticiper les nouvelles variables d'état lors du déploiement de nouvelles implémentations logiques. Après avoir ajouté une nouvelle variable d'état, la taille de l'écart doit être mise à jour de manière appropriée.

(4) Ne définissez pas la valeur de la variable d'état dans le constructeur ou le processus de déclaration

L'affectation d'une variable d'état lors de la déclaration ou dans le constructeur n'affecte que la valeur dans le contrat logique, pas le contrat proxy. Les paramètres non immuables doivent être assignés à l'aide de la fonction initialize().

** Héritage du contrat **

(1) Les contrats évolutifs ne peuvent hériter que d'autres contrats évolutifs

Les contrats évolutifs ont une structure différente des contrats non évolutifs. Par exemple, le constructeur n'est pas compatible avec la modification de l'état de l'agent, il utilise la fonction initialize() pour définir les variables d'état.

Tout contrat qui hérite d'un autre contrat doit utiliser la fonction initialize() de son contrat hérité pour affecter ses variables respectives. Lorsque vous utilisez la bibliothèque OpenZeppelin ou que vous écrivez votre propre code, assurez-vous que les contrats évolutifs ne peuvent hériter que d'autres contrats évolutifs.

(2) Ne pas instancier de nouveaux contrats dans des contrats logiques

Les contrats créés et instanciés via Solidity ne pourront pas être mis à niveau. Les contrats doivent être déployés individuellement et transmettre leur adresse en tant que paramètre au contrat de logique évolutive pour obtenir un état évolutif.

(3) Risque d'initialisation du contrat mère

Lors de l'initialisation du contrat Parent, la fonction __{ContractName}_init initialisera son contrat Parent. Plusieurs appels à __{ContractName}_init peuvent entraîner une deuxième initialisation du contrat parent. Notez que __{ContractName}_init_unchained() n'initialisera que les paramètres de {ContractName} et n'appellera pas l'initialiseur de son contrat parent.

Cependant, ce n'est pas une pratique recommandée, car tous les contrats parents doivent être initialisés, et ne pas initialiser les contrats requis entraînera des problèmes d'exécution futurs.

Mise en place du contrat logique

Évitez l'autodestruction() ou sélégatecall()/call() aux contrats non fiables

S'il y a selfdestruction() ou delegationcall() dans le contrat, il est possible qu'un attaquant utilise ces fonctions pour casser l'implémentation de la logique ou exécuter une logique personnalisée. Les développeurs doivent valider les entrées des utilisateurs et ne pas autoriser les contrats à exécuter des appels délégués/appels à des contrats non approuvés. De plus, il n'est pas recommandé d'utiliser delegatecall() dans les contrats logiques car il serait fastidieux de gérer la disposition du stockage dans la chaîne déléguée de plusieurs contrats.

Écrit à la fin

Les contrats proxy contournent la nature immuable des blockchains en permettant aux protocoles de mettre à jour leur logique de code après le déploiement. Cependant, le développement de contrats de procuration doit encore être très prudent, et une mise en œuvre incorrecte peut entraîner des problèmes de sécurité et de logique du projet.

Dans l'ensemble, la meilleure pratique consiste à utiliser des solutions faisant autorité et largement testées, car les modes Transparent, UUPS et Beacon Proxy ont chacun des mécanismes de mise à niveau éprouvés pour leurs cas d'utilisation respectifs. En plus de cela, les rôles privilégiés pour les agents en escalade doivent également être gérés en toute sécurité pour empêcher les attaquants de modifier la logique de l'agent.

Le contrat d'implémentation de la logique doit également veiller à ne pas utiliser delegatecall(), qui peut empêcher les attaquants d'exécuter du code malveillant, tel que selfdestruction().

Bien que le respect des meilleures pratiques garantisse des déploiements de contrats de proxy stables tout en conservant une flexibilité évolutive, tout le code est sujet à de nouveaux problèmes de sécurité ou de logique qui pourraient compromettre le projet. Par conséquent, tout le code est mieux audité par une équipe d'experts en sécurité ayant une expérience dans l'audit et la sécurisation des protocoles de contrat de proxy.

Voir l'original
Le contenu est fourni à titre de référence uniquement, il ne s'agit pas d'une sollicitation ou d'une offre. Aucun conseil en investissement, fiscalité ou juridique n'est fourni. Consultez l'Avertissement pour plus de détails sur les risques.
  • Récompense
  • Commentaire
  • Partager
Commentaire
0/400
Aucun commentaire
  • Épingler
Trader les cryptos partout et à tout moment
qrCode
Scan pour télécharger Gate.io app
Communauté
Français (Afrique)
  • 简体中文
  • English
  • Tiếng Việt
  • 繁體中文
  • Español
  • Русский
  • Français (Afrique)
  • Português (Portugal)
  • ไทย
  • Indonesia
  • 日本語
  • بالعربية
  • Українська
  • Português (Brasil)