Utilisé pour automatiser l'installation, la mise à niveau et la configuration des paquets, le gestionnaire npm est à risque. Ce dernier présente en effet un bogue de conception que les attaquants pourraient exploiter pour dissimuler des dépendances et des scripts malveillants à l'intérieur de ses paquets. Le problème, appelé « confusion de manifest », provient du manque de cohérence entre les fichiers manifest qui accompagnent les paquets archivés et le fichier de métadonnées json inclus dans le paquet lui-même. Le bug a été révélé publiquement cette semaine par Darcy Clarke, un ancien responsable de l'ingénierie de l'équipe npm CLI. Ce dernier a quitté Github - qui possède npm - en décembre 2022 mais a déclaré que la société était au courant de ce problème depuis novembre dernier, et qu'il l'a notifié à nouveau en mars 2023. Après une recherche indépendante, il est arrivé à la conclusion que l'impact était plus important que ce que l'on pensait à l'origine.

Selon Darcy Clarke, la communauté part généralement du principe que les manifests publiés avec un paquet sur le registre npm correspondent au contenu du fichier de métadonnées package.json inclus dans le paquet lui-même, l'archive tarball téléchargée à partir du dépôt. Ce n'est pas le cas et les gestionnaires de paquets JavaScript côté client tels que npm - mais aussi les outils de sécurité qui analysent les paquets à partir des dépôts npm - ne valident pas correctement ces fichiers les uns par rapport aux autres. Cela signifie que les paquets peuvent avoir des dépendances cachées ou des scripts d'installation listés dans leurs fichiers package.json mais pas dans le fichier manifest séparé. Ces dépendances et ces scripts seront analysés et exécutés par des clients JavaScript tels que l'interface de ligne de commande (CLI) npm et d'autres, même s'ils ne sont pas répertoriés dans le manifest du paquet. « Il y a plusieurs façons dont ce bogue a un impact sur les consommateurs/utilisateurs finaux : Empoisonnement du cache (c'est-à-dire que le paquet sauvegardé peut ne pas correspondre au nom et à la spécification de version de ce paquet dans le registre/URI), installation de dépendances inconnues/non listées (tromper les outils de sécurité/audit) ; exécution de scripts inconnus/non listés (tromper les outils de sécurité/audit) ; attaque potentielle par rétrogradation (où la spécification de version sauvegardée dans les projets correspond à une version vulnérable non spécifiée du paquet) », a déclaré M. Clarke.

Une validation dans les mains des installateurs de paquets

À la base, ce problème est dû au fait qu'il n'existe pas de « source canonique de vérité » claire pour les métadonnées d'un paquet, comme le nom, la version, les dépendances, les scripts, la licence, etc. Ces éléments sont spécifiés dans le fichier package.json qui est inclus dans l'archive du paquet elle-même et qui prend en charge les valeurs de vérification de l'intégrité telles que les hachages cryptographiques. Cependant, certaines de ces données peuvent être spécifiées dans le fichier manifest du paquet lors de sa publication sur le registre npm et ce manifeste dicte les informations que le registre affichera. Par exemple, Darcy Clarke a créé un exemple de paquet dont le fichier package.json mentionne un autre paquet comme dépendance, mais lorsqu'il l'a publié, il n'a pas inclus la dépendance dans le manifest. En conséquence, l'entrée du paquet sur le référentiel npm.js liste le paquet avec 0 dépendance, parce que le registre utilise le manifest comme source unique de vérité.

Cependant, le registre lui-même ne valide pas que les informations du package.json qui correspondent à celles du manifest. Cette tâche est laissée au client qui installe le paquet. Mais il s'avère qu'ils n'effectuent pas vraiment cette validation non plus... Par exemple, la version 6 de npm (npm@6), livrée avec la version 14 du moteur d'exécution node.js (support à long terme), exécutera un script d'installation défini dans le fichier package.json même si le script n'est pas défini dans le manifest. Une dépendance listée dans package.json et absente du manifest ne sera ainsi pas déployée lors du première téléchargement et de la première installation de paquet. Toutefois, si celui-ci est mis en cache localement et installé ultérieurement à partir de la source locale avec les options de ligne de commande -prefer-offline et -no-package-lock, les dépendances cachées du fichier package.json seront installées. Npm version 9 (npm@9), la version stable actuelle de npm, installera de la même manière les dépendances référencées dans le package.json d'un paquet mis en cache lors de l'utilisation de la configuration -offline.

Yarn et pnpm aussi vulnérables

Les gestionnaires de paquets yarn et pnpm, qui sont des alternatives à npm, sont également vulnérables et exécuteront les scripts référencés dans le fichier package.json qui sont absents du manifest. Yarn préférera également la version du paquet définie dans package.json à celle-ci. Comme ces deux valeurs peuvent être différentes, cela ouvre la porte à une attaque par rétrogradation. Ces dernières sont dangereuses, car un paquet peut être remplacé par une version plus ancienne qui présente une vulnérabilité connue. Ces itérations présentant des failles ne manquent pas, même dans les projets activement maintenus. La semaine dernière, des chercheurs de Snyk et de Redhunt Labs ont publié les résultats d'un projet de recherche qui a consisté à analyser plus de 11 000 dépôts appartenant aux 1 000 premières entreprises sur GitHub. L'analyse a cherché des failles dans les dépendances listées dans ces projets couvrant plusieurs langages de programmation. Pour JavaScript (npm et yarn), l'équipe a extrait 1,9 million de dépendances et identifié environ 550 000 cas de trous de sécurité connus.

Darcy Clarke pense que ce problème relève de différentes catégories de failles. Il note qu' « il y a une histoire qui repose fortement sur le client (aka le CLI de npm) pour faire le travail qui devrait être fait côté serveur ». Outre ces gestionnaires de paquets côté client, le problème touche également d'autres outils et registres de paquets tiers, y compris ceux axés sur la sécurité : Snyk, le miroir chinois NPM, le miroir CloudFlare npm CDN, le miroir UNPKG CDN, Skypack, JSPM, et même les dépôts locaux créés avec Artifactory de jFrog.

Pas de solution facile pour remédier à la vulnérabilité liée à la confusion des manifest

La résolution de ce problème et l'application soudaine de la validation n'est pas simple et pourrait prendre un certain temps jusqu'à ce que GitHub trouve une solution. Car il y a probablement de nombreux paquets qui présentent cette confusion, et ce n'est pas pour des raisons malveillantes. Darcy Clarke a noté que l'interface de programmation de npm elle-même provoque également de telles incohérences. Par exemple, lors de la publication d'un paquet via l'interface de commande npm où un fichier binding.gyp est situé à l'intérieur du projet, le client ajoutera une entrée au fichier manifest appelé : « node-gyp rebuild » scripts.install. Cette entrée ne sera pas présente dans le fichier package.json. « GitHub est logiquement dans une situation difficile », a déclaré le responsable. « Le fait que npmjs.com fonctionne de cette manière depuis plus de dix ans signifie que l'état actuel est assez codifié et susceptible de percer une défense de manière unique. Comme mentionné précédemment, l'interface de programmation npm elle-même repose sur ce comportement et il existe potentiellement d'autres utilisations non malveillantes dans la nature de ce comportement aujourd'hui ».

Les utilisateurs devraient contacter tous les auteurs connus d'outils qui s'appuient sur npm et leur demander de s'appuyer sur les informations du package.json plutôt que sur le manifest, à l'exception de la version et du nom qui peuvent être différents pour des raisons légitimes. Une autre option serait d'utiliser un proxy entre le client et le registre qui valide strictement les métadonnées des deux sources pour assurer la cohérence.