Skip to main content

Pourquoi les fichiers de verrouillage npm peuvent être un angle mort de sécurité pour l’injection de modules malveillants

Écrit par

24 septembre 2019

0 minutes de lecture

Je me suis récemment intéressé à l’idée de modéliser les menaces liées aux packages de l’écosystème npm. Un incident event-stream peut-il se reproduire ? Et qu’en est-il des autres attaques de la chaîne d’approvisionnement ? Quel sera le prochain vecteur d’attaque encore inconnu, et pourrait-on entièrement le prévenir ?

Puis, un jour, j’ai eu une révélation ! Laissez-moi vous montrer à quel point il est facile d’introduire des portes dérobées que les responsables de projet peuvent facilement manquer… et de laisser ainsi votre code vulnérable.

L’image suivante montre comment j’ai injecté un package malveillant par l’intermédiaire d’une pull request que j’ai soumise à un package open source sur npm.

Regardez de plus près et voyez si vous pouvez le repérer :

Demande de fusion GitHub affichant les mises à jour des versions des dépendances dans package.json et un diff yarn.lock réduit

Voici ce que donne ma checklist de révision des pull requests lorsque je l’applique à cette PR :

  • Les packages que j’ai ajoutés sont connus dans l’écosystème et ne présentent aucune vulnérabilité : vérifié.

  • Aucune tentative de typosquattage dans les noms de ces packages : vérifié.

  • Il s’agit de versions valides de ces packages, qui ne sont pas malveillantes en elles-mêmes : vérifié.

Tout me semble bon. Fusionnons !

Mais attendez. Avez-vous soigneusement vérifié ce fichier de verrouillage ? Bien sûr que non, et je ne vous en veux pas. Ce n’est vraiment pas évident, compte tenu des contraintes suivantes :

  • Par défaut, l’interface de GitHub replie les différences dès qu’elles dépassent quelques centaines de lignes de code.

  • À quoi bon examiner ce fichier ? Il est généré automatiquement et n’est pas très facile à lire.

Malgré ces contraintes, qu’est-ce qui pourrait vraiment mal tourner ?

Développons les différences pour voir ce qu’elles contiennent :

Demande de fusion GitHub affichant un diff de yarn.lock avec des mises à jour des versions de dépendances surlignées en rouge et en vert

Lorsqu’un fichier de verrouillage est présent, qu’il s’agisse du yarn.lock de Yarn ou du package-lock.json de npm, l’installation s’y réfère comme source de référence pour les versions des packages et leurs sources lors de l’installation des dépendances.

Vous voyez ce que je voulais dire ? Une modification de 500 lignes dans un fichier de verrouillage, c’est en fait assez peu. J’ai déjà vu des milliers de lignes modifiées dans un fichier package-lock.json npm ou un fichier yarn.lock Yarn. Enfin, qui prend vraiment le temps d’examiner des milliers de lignes de modifications caractère par caractère ? Pour ne rien arranger, dans ce fichier de verrouillage, nous avons constaté des changements de versions de dépendances et des mises à jour de métadonnées : rien de vraiment inhabituel, somme toute.

En faisant défiler un peu plus bas pour examiner le reste des modifications apportées à ce fichier, on trouve cette perle :

Diff d’une pull request GitHub montrant les modifications des dépendances dans yarn.lock, avec les lignes supprimées en rouge et les lignes ajoutées en vert

En y regardant de plus près, on comprend que j’ai remplacé le package npm ms d’origine par ma propre version, hébergée dans mon dépôt GitHub. J’aurais dû la récupérer depuis le registre npm officiel, comme l’indiquait le fichier de verrouillage d’origine du projet.

Ce projet utilise plusieurs versions de ms, mais je n’ai modifié que ms@2.1.1 pour utiliser ma version personnalisée :

yarn list --pattern ms
yarn list v1.17.3
├─ @babel/helper-module-transforms@7.4.4
├─ debug@2.6.9
│  └─ ms@2.0.0
├─ express@4.17.1
│  └─ ms@2.1.1
├─ ms@2.1.2
├─ send@0.16.2
│  └─ ms@2.0.0
├─ serve-static@1.14.1
│  └─ ms@2.1.1
✨  Done in 0.49s.

Il est difficile de penser à examiner les changements à ce niveau de détail — et il n’est pas étonnant que si peu de personnes vérifient réellement les modifications des fichiers de verrouillage caractère par caractère !

Alors, que fait ma version de ms@2.1.1 ?

Lorsque cette pull request sera fusionnée, j’injecterai ma version malveillante de ms@2.1.1 dans le code afin d’en contrôler le comportement à l’exécution.

De cette façon, je pourrais introduire une porte dérobée, modifier la logique du module ms ou exécuter des scripts postinstall. De plus, l’URL d’hébergement https://github.com/lirantal/ms/tarball/master est évidemment suspecte pour cette expérience. Mais la repéreriez-vous facilement si j’achetais un domaine typosquatté pour l’héberger à l’adresse https://registry.npmgs.org/ms/ ?

Pour cette expérience, mon package ms hébergé contient un simple script postinstall qui affiche du texte et écrit dans un fichier. Ainsi, si vous installiez le package lui-même, vous pourriez vérifier que l’événement post-installation s’exécute simplement lors d’un yarn install :

"scripts": {
   "postinstall": "echo im installed && echo hello > /tmp/world.txt",
}

Pour les applications basées sur un projet, la sécurité des fichiers de verrouillage est essentielle et vulnérable aux attaques par injection malveillante décrites dans cet article.

En revanche, dans les packages utilisés comme bibliothèques, les fichiers de verrouillage sont uniquement destinés au processus de développement du package lui-même. C’est pourquoi, lorsque le projet (package) est installé comme dépendance dans un autre projet, son fichier de verrouillage n’est pas utilisé. Cela signifie que, pour les bibliothèques, l’injection dans un fichier de verrouillage mettrait principalement en danger les responsables et les collaborateurs du projet.

Comment renforcer la sécurité des fichiers de verrouillage ?

Les fichiers de verrouillage étant destinés à être lus par des machines, ils sont faciles à traiter automatiquement. C’est donc la tâche idéale pour un linter et des règles prédéfinies !

Pour contrer ce vecteur d’attaque, j’ai créé lockfile-lint. J’ai ajouté cet outil comme étape rapide supplémentaire à l’analyse statique du code, que vous pouvez exécuter sur un serveur CI tel que Travis CI ou Circle CI.

Au minimum, vous devez valider que toutes les ressources sont fournies :

  • via HTTPS

  • depuis une source de confiance

Par exemple, pour exécuter ces vérifications en CI sur un projet Yarn, il suffit de :

$ npx lockfile-lint --path yarn.lock --type yarn --validate-https --allowed-hosts yarnpkg.org

Voici un exemple d’erreurs courantes détectées lors de l’analyse du fichier de verrouillage d’un projet :

Sortie du terminal montrant lockfile-lint valider un fichier yarn.lock, détecter quatre URL de packages non HTTPS et se terminer avec le code 1.

En résumé

Les attaques peuvent prendre de nombreuses formes… même passer par des fichiers de verrouillage !

Avec l’incident event-stream, nous avons assisté à la première attaque par ingénierie sociale réussie contre des responsables de projets open source. Il s’agissait peut-être de l’une des attaques les plus sophistiquées contre la sécurité de la chaîne d’approvisionnement logicielle que l’écosystème npm et JavaScript ait connues à ce jour.

Une injection dans un fichier de verrouillage pourrait procéder de manière similaire. Vous devez donc mettre en place de bonnes pratiques pour atténuer ce problème, ou au moins réduire les risques. Voici quelques pistes :

  • Examinez soigneusement les modifications apportées aux fichiers de verrouillage (lockfile-lint peut vous aider).

  • Autorisez les responsables principaux, et non les contributeurs occasionnels, à modifier les fichiers de verrouillage.

  • Évitez complètement les fichiers de verrouillage pour les bibliothèques.

Pour en savoir plus sur les fichiers de verrouillage et leur utilisation, j’ai rédigé un article détaillé sur les fichiers de verrouillage des packages dans l’écosystème npm.

Lancez-vous dans les compétitions Capture The Flag

Apprenez à résoudre des défis Capture The Flag en regardant à la demande notre atelier virtuel d’initiation.

Lire la suite

blog feature snyk container custom base recommendations
Blog

Découvrez Evo Agentic AppSec : une sécurité au rythme du développement de l’IA

Le développement piloté par l’IA dépasse les workflows de sécurité traditionnels. L’AppSec agentique de Snyk s’appuie sur des agents de sécurité pour détecter, corriger et vérifier les vulnérabilités en continu, tandis que les équipes définissent les exigences.

blog feature ai violet
Blog

VulnBump : les agents IA peuvent-ils corriger les dépendances vulnérables sans casser le build ?

VulnBump évalue des agents IA sur 307 mises à niveau de dépendances réelles. Une correction n’est comptabilisée que si la vulnérabilité disparaît et que la compilation et les tests restent au vert.

Blog

Les modèles de pointe ont trouvé les vulnérabilités. Seul l’attaquant a trouvé les chaînes d’exploitation.

L’analyse statique a détecté les failles, mais seuls des tests d’attaque en conditions réelles ont prouvé comment elles pouvaient être enchaînées pour provoquer des compromissions. Comparaison d’Evo COS, de Claude Security et de Claude Code Security.