Skip to main content

La menace mystérieuse qui pèse sur la chaîne logistique du package npm string-width-cjs

Écrit par
feature snyk supply chain purple

3 octobre 2024

0 minutes de lecture

Tout 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 :

alias de package npm dans une pull request

Plus précisément, c’est la modification des dépendances npm qui attire notre attention, car elle utilise une syntaxe inhabituelle :

  "dependencies": {
    "string-width": "^5.1.2",
    "string-width-cjs": "npm:string-width@^4.2.0",
    "strip-ansi": "^7.0.1",
    "strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
    "wrap-ansi": "^8.1.0",
    "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"

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 :

npx lockfile-lint --path package-lock.json --allowed-hosts yarn npm --validate-https --validate-package-names

detected resolved URL for package with a different name: string-width-cjs
    expected: string-width-cjs
    actual: string-width

detected resolved URL for package with a different name: strip-ansi-cjs
    expected: strip-ansi-cjs
    actual: strip-ansi

detected resolved URL for package with a different name: wrap-ansi-cjs
    expected: wrap-ansi-cjs
    actual: wrap-ansi

 ✖ Error: security issues detected!

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.

Paquet suspect strip-ansi-cjs sur npm

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 ?

Le package strip-ansi-cjs ne contient aucun code source

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-cliui pourrait être une tentative de typosquattage visant le fork du projet cliui d’Isaac et le package npm légitime publié sous son espace de noms : @isaacs/cliui.

  • Le package npm azure-sdk-for-net pourrait être une tentative d’attaque par confusion de dépendances visant des packages privés portant le même nom.

  • Le package npm link-deep usurpe le nom d’une fonctionnalité populaire associée à des packages utilitaires comme lodash.

Packages npm appartenant à un mainteneur suspect sur npm

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 :

Packages dépendants du registre npmjs

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 :

Page du package npm react-native-multiply présentant 776 dépendances, la commande d’installation, un exemple de code et le nombre de téléchargements hebdomadaires.

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 ».

Explorateur de fichiers affichant le répertoire du projet /react-native-multiply/, avec les dossiers Android, C++, iOS, library et source, ainsi que des fichiers de package.

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 :

import { multiply } from 'react-native-multiply';
const result = await multiply(3, 7);

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 :

Extrait de code affichant la version ^5.1.1 du package npm string-width-cjs parmi des dépendances connexes de manipulation de chaînes

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

Page du package npm class-transformer présentant son fichier README, sa commande d’installation, ses dépendances, sa version, sa licence et ses statistiques de téléchargement.

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 :

Éditeur de code sombre affichant le manifeste de package JSON de class-transformer, version 0.5.1, avec les détails du dépôt et du module

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 :

Vue du code d’un package npm affichant une liste JSON de dépendances pour clazz-transformer, avec les noms des packages et leurs plages de versions

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 fonction multiply par 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 :

Comment sur un forum de tea abuse : la plupart des projets ne sont que du spam inutile

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.