Pourquoi les fichiers de verrouillage npm peuvent être un angle mort de sécurité pour l’injection de modules malveillants
24 septembre 2019
0 minutes de lectureJe 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 :

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 :

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 :

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 :
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 :
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 :
Voici un exemple d’erreurs courantes détectées lors de l’analyse du fichier de verrouillage d’un projet :

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.


