La menace mystérieuse qui pèse sur la chaîne logistique du package npm string-width-cjs
3 octobre 2024
0 minutes de lectureTout commence lorsque Sébastien Lorber, mainteneur de Docusaurus, le projet open source de documentation basé sur React, remarque une modification du manifeste de package dans une pull request. Voici la modification proposée pour le célèbre package npm cliui :

Plus précisément, c’est la modification des dépendances npm qui attire notre attention, car elle utilise une syntaxe inhabituelle :
La plupart des développeurs s’attendraient à voir une plage de versions semver dans la valeur d’un package, ou éventuellement une URL Git ou un chemin vers un fichier. Or, dans ce cas, on trouve une syntaxe spéciale avec le préfixe npm:. Qu’est-ce que cela signifie ?
Qu’est-ce que l’aliasage des packages npm ?
Le gestionnaire de packages npm prend en charge une fonctionnalité d’aliasage qui permet de définir des règles de résolution personnalisées pour les packages. Ainsi, chaque fois qu’un package est référencé, dans le code ou le fichier de verrouillage, il est résolu vers le nom et la version spécifiés par l’alias.
Dans le cas de la modification proposée dans cette pull request, le package string-width-cjs sera donc résolu vers le package string-width dans les versions ^4.2.0. Cela signifie qu’une entrée string-width-cjs sera créée dans le répertoire node_modules, mais qu’elle contiendra string-width@^4.2.0. Le fichier de verrouillage (package-lock.json) adoptera un comportement similaire.
L’aliasage des packages est une fonctionnalité du gestionnaire de packages npm qui peut être utilisée, par exemple, pour prendre en charge ESM et CJS.
Cela dit, l’aliasage des packages peut être détourné. Dans un article et une divulgation de sécurité datant de 2021, Nishant Jain, ambassadeur Snyk, a montré comment le registre officiel npmjs pouvait être trompé et fournir des informations erronées sur les dépendances à l’aide de l’aliasage de packages, dans le cadre d’un problème de confusion de dépendances et de sécurité de la chaîne logistique.
La pull request était sans danger et ne présentait aucun risque d’attaque de la chaîne logistique. Cependant, les inquiétudes de Sébastien au sujet du nom du package ont permis de découvrir un risque de sécurité potentiel.
Détecter les comportements suspects dans les fichiers de verrouillage npm liés à des modules malveillants
Pour examiner la pull request, Sébastien a utilisé lockfile-lint. Cet outil vérifie les fichiers de verrouillage comme package-lock.json ou yarn.lock afin d’y repérer des signes d’altération et de s’assurer qu’aucun package malveillant n’a été injecté à la place du package npm d’origine.
L’exécution de l’outil a affiché les avertissements suivants :
Avertissement : j’ai créé lockfile-lint en 2019, à la suite de la publication dans laquelle je signalais les risques de sécurité liés aux fichiers de verrouillage : Pourquoi les fichiers de verrouillage npm peuvent être un angle mort de sécurité permettant l’injection de modules malveillants.
Alerte élevée : des sosies de packages populaires sur npm
À la lumière des résultats de lockfile-lint, Sébastien a recherché ces noms de packages sur npm et a découvert, à sa grande surprise, qu’ils existaient bien dans le registre npm public :
https://www.npmjs.com/package/string-width-cjs
https://www.npmjs.com/package/strip-ansi-cjs
https://www.npmjs.com/package/wrap-ansi-cjs
Sébastien a constaté que ces noms de packages existaient non seulement sur npm, mais présentaient également des caractéristiques suspectes. Ces packages n’étaient associés à aucun dépôt de code source public, ne contenaient aucun code lorsqu’on les examinait et avaient été publiés anonymement, sans aucune information personnelle.
Le package npm strip-ansi-cjs ne comporte ni fichier README ni dépôt de code source. Pourtant, de nombreux packages légitimes et populaires présentent le même comportement.
En fait, ce package est populaire, comme en témoignent ses 529 dépendants (d’autres packages qui en dépendent) et ses 7 274 téléchargements hebdomadaires.

Le code de strip-ansi-cjs montre que ce package ne contient qu’un seul fichier : le manifeste package.json.
Alors, pourquoi un package qui ne fait rien totalise-t-il autant de téléchargements, et pourquoi tant d’autres packages en dépendent-ils ?

Examinons l’auteur de ces packages npm.
Les trois packages appartiennent à himanshutester002 et ont tous été publiés l’année dernière, avec des numéros de version générés automatiquement. Voici quelques observations intéressantes :
Le package npm
isaacs-cliuipourrait être une tentative de typosquattage visant le fork du projetcliuid’Isaac et le package npm légitime publié sous son espace de noms : @isaacs/cliui.Le package npm
azure-sdk-for-netpourrait être une tentative d’attaque par confusion de dépendances visant des packages privés portant le même nom.Le package npm
link-deepusurpe le nom d’une fonctionnalité populaire associée à des packages utilitaires comme lodash.

Vous remarquerez également que le profil npmjs de l’utilisateur himanshutester002 ne contient aucune information permettant de l’identifier.
Nous avons indiqué précédemment que plus de 500 autres packages utilisent le package npm strip-ansi-cjs, ce qui pourrait laisser penser qu’il est populaire. Examinons-les :

Sa présence dans la liste peut sembler convaincante, mais l’est-elle vraiment ?
Des noms comme clazz-transformer, react-native-multiply ou gh-monoproject-cli paraissent légitimes, mais le sont-ils ?
Voici la page npm du package react-native-multiply :

Ce package n’a pratiquement aucun téléchargement et son auteur est un utilisateur anonyme de npm, sans aucune information permettant de l’identifier. L’URL source vers laquelle ce package redirige mène au dépôt inexistant https://github[.]com/hasandader/react-native-multiply. Le profil GitHub de l’utilisateur paraît également très suspect et ne montre aucune activité concrète.
Bien que le package npm semble contenir du code source, un examen plus attentif révèle qu’il s’agit d’un exemple de code généré pour un prototype d’application « hello world ».

On peut aussi se demander pourquoi ce package, qui n’est qu’une bibliothèque de multiplication, a besoin de 776 dépendances pour effectuer l’opération suivante :
Certains plaisantent en disant que JavaScript contribue à la croissance astronomique des arbres de packages imbriqués à force d’utiliser trop de dépendances. Mais un projet comptant 776 dépendances directes est démesurément volumineux.
Parmi toutes ces dépendances figurent les trois packages npm suspects à l’origine de notre enquête : string-width-cjs, strip-ansi-cjs et wrap-ansi-cjs :

Nous avons mentionné qu’une des dépendances de strip-ansi-cjs s’appelait clazz-transformer. Examinons-la :

Expliquons ce qui se passe ici. Le package npm clazz-transformer porte intentionnellement un nom trompeur et se présente sous le titre class-transformer sur sa page README. De plus, son dépôt de code source, https://github[.]com/typestack/class-transformer, ne correspond pas au nom du package, ce qui remet en question sa légitimité.
Le dépôt associé typstack/class-transformer sur GitHub contient le fichier package.json suivant :

Le fichier package.json sur GitHub ne déclare aucune dépendance. Pourtant, si nous examinons le code source du package publié sur npmjs, nous constatons que ce clazz-transformer est livré avec 437 dépendances. Une fois encore, il regroupe très opportunément les trois packages suspects *-cjs :

Autres réflexions sur les packages npm suspects découverts
Avant de tirer d’autres conclusions, il est important de mentionner quelques caractéristiques des packages npm observés ci-dessus :
Les packages React Native semblent dérivés de l’outil de création de squelettes
create-react-native-library. Cet outil inclut également la fonctionmultiplypar défaut dans le code source généré pour un nouveau projet.Les packages présentent des structures de répertoires, des fichiers et des dépendances qui pourraient provenir du modèle de démarrage Next.js 14, comme ceux créés avec
npx create-next-app@14.
Nos collègues de Sonatype ont déjà repéré des cas similaires d’inondation des registres open source par des packages. Dans ces cas, l’objectif final était que les développeurs s’attribuent des jetons Tea, une plateforme Web3 de monétisation des logiciels open source.
La présence de fichiers tea.yaml dans les packages mentionnés renforce l’hypothèse qu’une partie de cette campagne vise à générer des jetons Tea en détournant la plateforme Tea.
Plus tôt cette année, le 14 avril 2024, un utilisateur du forum Tea a publié un commentaire qui renforce les inquiétudes quant à un possible abus de Tea :

Avant de conclure, je tiens à remercier sincèrement Sébastien Lorber pour sa vigilance en tant que mainteneur et pour avoir contribué à révéler les indices d’une éventuelle attaque de la chaîne logistique npm.
Que se passe-t-il avec string-width-cjs ?
À ce stade, je suis presque certain qu’en continuant à examiner les autres packages censés dépendre de string-width-cjs, je trouverai des indices très douteux quant à leur légitimité.
Je suppose que tous ces packages dépendants et ces hausses de téléchargements visent uniquement à donner une fausse légitimité aux trois packages *-cjs. Ainsi, le moment venu, lorsque la victime idéale se présentera, ces faux packages seront installés, puis une nouvelle version malveillante sera publiée.
Pour assurer votre sécurité lorsque vous travaillez avec des logiciels open source, je vous recommande vivement d’adopter des pratiques de sécurité et de consulter les ressources pédagogiques suivantes :
Avons-nous découvert une campagne visant à compromettre la sécurité de la chaîne logistique, ou tout cela est-il motivé par l’appât du gain et relève-t-il plutôt du spam et de l’abus de registres publics comme npm et GitHub pour générer des jetons Tea ?
Quelle que soit la suite, restez vigilants.
Sécurisez votre code pendant le développement
Snyk analyse votre code pour détecter les problèmes de qualité et de sécurité, et vous conseille sur leur correction directement dans votre IDE.
