Après trois ans de silence, une nouvelle vulnérabilité de pollution de prototype dans jQuery refait surface
15 avril 2019
0 minutes de lectureLe 26 mars 2019, près de trois ans après la dernière vulnérabilité de sécurité dans jQuery, nous avons récemment appris l’existence d’une nouvelle vulnérabilité de sécurité affectant cette même bibliothèque frontend jQuery très populaire.
Cette vulnérabilité de sécurité, appelée pollution de prototype, permet aux attaquants de remplacer le prototype d’un objet d’une application JavaScript. Dans ce cas, des propriétés contrôlées par l’attaquant peuvent être injectées dans les objets et entraîner un déni de service en déclenchant des exceptions JavaScript, ou altérer le code source de l’application afin de forcer l’exécution du chemin de code injecté par l’attaquant.
Voici un exemple de preuve de concept tiré du rapport d’origine que j’ai évalué sur HackerOne dans le cadre du groupe de travail (WG) sur la sécurité de Node.js :
Comme le montre le code, l’API étendue de jQuery sert à fusionner récursivement plusieurs objets.
Même si cette vulnérabilité n’est pas très facile à exploiter, elle peut potentiellement affecter un grand nombre de projets et d’utilisateurs, en raison de la popularité de jQuery dans l’écosystème JavaScript. De plus, nous avons déjà observé des attaques de pollution de prototype dans le monde réel, comme celle qui a affecté mongoose en décembre 2018.
L’équipe jQuery a récemment publié un correctif pour ce problème de sécurité dans la version 3.4.0. Nous vous recommandons vivement de procéder à la mise à niveau.
Reconstituer une application vulnérable
Puisque jQuery est une bibliothèque principalement utilisée côté frontend, voyons comment une vulnérabilité de pollution de prototype se manifeste dans une application côté client.
L’attaque commence par une entrée utilisateur qui permet à un attaquant malveillant d’injecter un objet que le développeur n’a peut-être pas assaini ni prévu de traiter de manière particulière.
Imaginons que vous développiez une application dans laquelle un utilisateur est autorisé à envoyer des charges utiles JSON, enregistrées telles quelles. Vous pourriez vouloir lui accorder cette possibilité pour qu’il puisse contrôler la structure du contenu, dont vous ne souhaitez pas être responsable.
Voici un exemple de charge utile qui pourrait être envoyée dans ce cas :
Imaginons que vous deviez cloner un objet de manière récursive, sans savoir exactement comment vous y prendre. Si vous recherchez « deep clone an object in javascript » sur Google, cette réponse apparaît en tête des résultats de recherche Stack Overflow :
En voyant qu’elle a recueilli plus de quatre mille votes positifs et qu’elle a été choisie comme bonne réponse, vous pourriez être tenté de copier-coller l’exemple de copie profonde pour terminer le travail.
D’après les conseils de Stack Overflow, à votre avis, que se passerait-il avec cet exemple de code ?
Un
myObjectillustre un format sous forme de chaîne, éventuellement récupéré dans un champ de base de données.JSON.parse()et la fonction jQueryextend()sont utilisées pour créer une copie demyObject.La nouvelle copie s’appelle
newObject.
Lorsque JSON.parse() traite une propriété nommée __proto__, comme dans notre exemple, vous pourriez vous attendre à ce qu’il attribue la propriété isAdmin à true dans la propriété parente de l’objet. En réalité, il crée un objet portant ce nom de propriété, ce qui remplace la capacité de chaînage des propriétés.
Lorsque ce comportement de JSON.parse() est associé à une fonction de clonage profond non sécurisée, autrement dit à une fusion d’objets, la valeur attribuée à la propriété __proto__ se retrouve dans l’objet JavaScript global.
Dans cette optique, examinons l’extrait de code suivant et ses conséquences pour comprendre le fonctionnement des attaques par pollution de prototype :
Si l’objet utilisateur récupéré dans la base de données ne possède aucune valeur définie pour sa propriété isAdmin, alors la valeur de l’objet utilisateur est en quelque sorte indéfinie. Dans ce cas, accéder à la propriété isAdmin dans la clause if nécessite de remonter à l’objet parent dans la chaîne de prototypes de l’objet user. Il s’agit de Object, qui a maintenant été contaminé et comprend cette propriété isAdmin, définie à true. Ainsi, sans que le développeur ne l’ait voulu, l’utilisateur se retrouve administrateur et peut semer le chaos dans l’application.
Explorer d’autres vecteurs d’attaque
Comme nous venons de le voir dans l’exemple précédent, une opération de fusion récursive non sécurisée, associée au fonctionnement de JSON.parse, peut entraîner une pollution de la chaîne de prototypes.
Ce n’est toutefois pas la seule façon de modifier le prototype. Prenons le code suivant :
Dans cet exemple :
Un nouvel objet
myObjest créé.La chaîne de prototypes est accessible via
__proto__et cet objet est modifié pour inclure une nouvelle propriété de type chaîne.
Comme le prototype de myObj est en réalité un Object JavaScript que nous avons modifié, tous les nouveaux objets créés à partir de maintenant incluront également cette propriété. Cela s’explique par le fonctionnement de JavaScript : si l’objet a ne possède pas cette propriété (sur newObj), JavaScript consulte le prototype de newObj pour la trouver, puis poursuit récursivement jusqu’à avoir parcouru toute la chaîne de prototypes. Dans notre cas, cette propriété existe, ce qui explique pourquoi l’accès renvoie la valeur de type chaîne.
Vous vous demandez peut-être comment quelqu’un pourrait injecter un accès à l’objet __proto__ de la manière que nous venons de décrire.
Imaginons que nous développions une application qui répertorie des packages sur npm et les utilise, par exemple pour les afficher dans une interface utilisateur soignée.
Nous aurions probablement besoin d’une API qui envoie une liste de packages npm et le contenu de leurs fichiers package.json :
Vous pourriez penser que cette API est sécurisée parce qu’elle provient directement de npmjs et des API fournies par ce service, ou d’un autre site de confiance proposant ces données, dont l’attaquant ne contrôle pas l’API. Ce serait une erreur. Comme vous l’avez probablement remarqué, les détails des packages, tels que leur nom et le contenu de package.json, sont en fin de compte sous le contrôle de l’utilisateur.
Pour poursuivre avec cet exemple d’application, imaginons que le développeur souhaite créer une map à partir du tableau afin d’accéder facilement aux packages sans devoir parcourir le tableau pour récupérer les données.
En pratique, le développeur pourrait tenter d’accéder aux données de la manière suivante :
Voici un exemple fonctionnel de ce code :
Si nous créions alors de nouveaux objets et tentions d’accéder à leur propriété toString, nous mettrions au jour l’attaque par pollution de prototype.
La pollution de prototype à grande échelle
jQuery n’est pas la seule victime de ce type de vulnérabilité. Au cours de la seule année passée, nous avons recensé plus de 20 vulnérabilités de pollution de prototype dans les écosystèmes des navigateurs et de Node.js. Ces vulnérabilités touchent aussi bien des bibliothèques JavaScript comme lodash, susceptibles d’affecter de nombreux projets frontend en raison de leur popularité, que des bibliothèques backend Node.js qui gèrent le clonage d’objets JavaScript, comme node.extend et deep-extend.
Le 21 février, Eran Hammer a également raconté comment une attaque similaire, une vulnérabilité d’empoisonnement de prototype, peut affecter une bibliothèque en exposant des données via joi, un package de validation populaire utilisé par de nombreux projets de l’écosystème. Cette vulnérabilité a également touché hoek, une bibliothèque utilitaire créée par le même groupe de développeurs dans le cadre du framework d’applications Web hapi.
Bon nombre de ces vulnérabilités de pollution de prototype ont été signalées par Olivier Arteau, également connu sous le nom de HoLyVieR, dans le cadre d’une divulgation responsable auprès du programme HackerOne géré par le groupe de travail sur la sécurité de Node.js. Ce programme vise à assurer la réponse aux incidents et à traiter les problèmes de sécurité qui touchent l’ensemble de l’écosystème JavaScript. Oliver a également publié un rapport détaillé sur la vulnérabilité, consacré aux conséquences de la pollution de prototype, et présenté un cas concret affectant le projet Ghost CMS sous Node.js lors de la conférence NorthSec.
En résumé
Pour conclure, voici plusieurs mesures d’atténuation et bonnes pratiques de sécurité à suivre pour éviter la pollution de prototype :
Veillez à utiliser des implémentations sûres de fusion récursive.
Envisagez de créer des objets sans prototype, par exemple avec
Object.create(null), pour éviter qu’ils ne soient vulnérables aux attaques par pollution de prototype.Évitez d’utiliser la notation entre crochets avec des données contrôlées par l’utilisateur et, si possible, évitez-la tout court. Pour les structures basées sur des maps, envisagez d’utiliser la primitive de langage
Map.
Chez Snyk, nous accordons une grande importance à la communauté de la sécurité et estimons que la divulgation responsable des vulnérabilités dans les packages open source contribue à protéger la sécurité et la confidentialité des utilisateurs. Si vous pensez avoir découvert une vulnérabilité dans un package open source, vous pouvez nous contacter à l’adresse https://snyk.io/vulnerability-disclosure. Notre programme de divulgation responsable vise à protéger à la fois le développeur et le chercheur qui signale la vulnérabilité, tout en permettant aux développeurs de tirer parti en toute sécurité des vulnérabilités découvertes par les chercheurs.
Lancez-vous dans les compétitions Capture The Flag
Apprenez à résoudre des défis de capture du drapeau en regardant à la demande notre atelier virtuel d’initiation.
