Sécurité de NPM : prévenir les attaques de la chaîne d’approvisionnement
8 novembre 2022
0 minutes de lectureLa sécurité de NPM est un sujet qui fait souvent la une des médias ces dernières années, principalement en raison des packages npm disponibles dans l’écosystème, plutôt que du registre npm lui-même. Le risque croissant pour la sécurité, qui concerne les développeurs et les logiciels que nous créons, rend d’autant plus important de comprendre comment prévenir les attaques de la chaîne d’approvisionnement et les autres vulnérabilités liées au cycle de vie du développement logiciel.
Les attaques de la chaîne d’approvisionnement prennent de nombreuses formes : attaques par confusion de dépendances, introduction de portes dérobées malveillantes dans des packages open source et compromission de l’infrastructure des pipelines de build, entre autres. Les risques de sécurité présents dans les bibliothèques et les écosystèmes open source représentent une menace immédiate pour les développeurs.
Quels contrôles de sécurité logicielle pouvons-nous mettre en place pour renforcer notre posture de sécurité ? Dans cet article, je vous présente les pratiques de sécurité npm et les outils de sécurité de la chaîne d’approvisionnement à la disposition des développeurs JavaScript (les développeurs TypeScript sont également les bienvenus).
État des lieux des attaques de la chaîne d’approvisionnement en 2022
Nous constatons de plus en plus à quel point les développeurs jouent un rôle clé dans la sécurité des applications. Ils sont directement impliqués dans la multiplication des incidents de sécurité, comme le module peacenotwar, l’attaque par confusion de dépendances contre gmx-reference et les attaques par confusion de dépendances de Cobalt Strike, qui ne sont que quelques exemples récents ayant fait la une de l’actualité sur la sécurité de npm.
À votre avis, combien de dépendances npm et de mainteneurs différents vos projets comptent-ils ? Une étude universitaire publiée le 7 juin 2019 apporte des informations intéressantes à ce sujet :
L’installation d’un package npm moyen implique de faire confiance à 79 packages tiers et à 39 mainteneurs, ce qui crée une surface d’attaque étonnamment vaste.
Les acteurs malveillants qui se consacrent aux attaques de la chaîne d’approvisionnement trouvent sans cesse de nouveaux moyens de s’infiltrer dans un écosystème et d’y introduire des logiciels malveillants. Un exemple marquant est une étude de sécurité récente publiée en 2021, qui a révélé que :
2 818 adresses e-mail de mainteneurs étaient associées à des domaines expirés, ce qui permettait à un attaquant de détourner 8 494 packages en prenant le contrôle des comptes npm. […] Nous avons constaté que 58,7 % des packages et 44,3 % des mainteneurs sont inactifs dans le registre npm.
Malgré ces préoccupations, les logiciels open source contribuent largement à de nombreuses technologies innovantes et aident les entreprises à accélérer la livraison de leurs produits. Les logiciels open source sont un formidable cadeau pour le monde, mais il reste important d’attirer l’attention sur les risques liés aux bibliothèques logicielles tierces et sur l’ampleur de leur impact lorsqu’elles sont intégrées à nos applications.
Qu’est-ce que la sécurité de la chaîne d’approvisionnement ?
Il est courant que les développeurs envisagent la sécurité de la chaîne d’approvisionnement open source en se concentrant sur les points de contact immédiats, comme les packages npm que nous installons (npm install event-stream) et importons dans nos projets (import colors). Pourtant, la surface d’attaque de la chaîne d’approvisionnement logicielle est bien plus vaste.
Qu’est-ce qu’une attaque de la chaîne d’approvisionnement ?
Dans le contexte du développement logiciel, une attaque de la chaîne d’approvisionnement prend la forme d’acteurs malveillants qui injectent des logiciels malveillants, des portes dérobées ou d’autres charges utiles dans des composants logiciels ou une infrastructure liée aux logiciels, ensuite utilisés pour produire d’autres logiciels fonctionnels. Autrement dit, les acteurs malveillants n’exploitent pas directement les vulnérabilités du code source du logiciel ciblé et ne mènent pas de recherches à leur sujet.
Les niveaux de la chaîne d’approvisionnement des artefacts logiciels, également connus sous le nom de SLSA, constituent une bonne référence pour repérer les points d’intégration faibles où se cachent des risques. Voici une représentation visuelle tirée du livre blanc de Snyk sur la sécurité de la chaîne d’approvisionnement :

Revenons aux bases de la création logicielle telle qu’elle se pratique aujourd’hui : bon nombre des étapes habituelles de production logicielle sont exposées à des menaces bien réelles. L’article suivant, publié sur le blog de sécurité de Google, présente plusieurs exemples d’incidents de sécurité réels pour chacune de ces étapes de livraison logicielle.
Sécurité de NPM et mesures préventives pour la sécurité de la chaîne d’approvisionnement
(1) Prévenir l’injection dans les fichiers de verrouillage NPM
En septembre 2019, j’ai publié les résultats de mes recherches en sécurité, qui décrivaient les risques inhérents aux fichiers de verrouillage des packages dans l’écosystème npm. Les deux gestionnaires de packages JavaScript, Yarn et npm, se sont révélés vulnérables.
Cette menace survient lorsque des acteurs malveillants obtiennent l’accès et la possibilité de contribuer au code source, par exemple au moyen de pull requests, couramment utilisées sur GitHub pour contribuer à des projets open source. Dans ce contexte, s’ils modifient un fichier de verrouillage comme package-lock.json ou yarn.lock pour y ajouter une dépendance à un nouveau package npm, ou même simplement pour changer l’URL source d’un package existant, toute installation de packages (comme npm install ou yarn install) récupérera le logiciel malveillant.
La capture d’écran suivante montre comment j’ai injecté un package malveillant au moyen d’une pull request envoyée à un package open source sur npm. Examinez-la attentivement pour voir si vous le repérez :

Vous vous dites peut-être que vous avez vérifié les points suivants :
Les packages que j’ai ajoutés sont connus dans l’écosystème et ne présentent aucune vulnérabilité : vérifié.
Les noms de ces packages ne font l’objet d’aucune tentative de typosquattage : vérifié.
Il s’agit de versions valides de ces packages, qui ne sont pas malveillantes en elles-mêmes : vérifié.
Et pourtant, vous ne pourriez toujours pas le repérer.
Essayez de développer le fichier yarn.lock. Un fichier de verrouillage de gestionnaire de packages est généré par une machine et n’est pas conçu pour être lisible ou maintenable par un être humain. Il est donc très difficile d’y repérer des changements comme la mise à jour malveillante que j’y ai injectée :

De plus, les gestionnaires de packages JavaScript permettent d’installer des packages à partir de sources très inhabituelles, comme un gist GitHub ou directement depuis un dépôt de code source.
Je peux donc simplement modifier le fichier de verrouillage pour indiquer une nouvelle source (par exemple dans la clé resolved) que je contrôle entièrement en tant qu’attaquant. Je peux aussi définir la valeur d’intégrité SHA512 correspondante afin qu’aucune alerte ne se déclenche.
Si cette pull request est fusionnée, le prochain collaborateur du projet qui exécutera yarn install récupérera effectivement mon package malveillant. Mais, en réalité, il n’est même pas nécessaire d’attendre qu’un développeur interagisse avec le projet : de nombreux projets open source matures disposent d’un environnement d’intégration continue (CI).
Il est fort probable que la simple création de cette pull request déclenche un build automatisé sur GitHub Actions (ou peut-être CircleCI). Lors du build de cette pull request, le processus npm install s’exécutera, ce qui pourrait me permettre d’accéder à des informations sensibles, comme les secrets de votre environnement CI, vos clés API, etc., et de les dérober.
Pour prévenir ce type d’attaque par injection dans un fichier de verrouillage, vous pouvez utiliser lockfile-lint. Avec lockfile-lint, les développeurs peuvent vérifier leurs fichiers de verrouillage et s’assurer que ces instantanés générés automatiquement des dépendances épinglées respectent une politique de confiance donnée.
Par exemple, vous pouvez configurer lockfile-lint pour qu’il échoue s’il détecte que le fichier de verrouillage yarn.lock indique des sources de packages npm autres que le miroir officiel yarnpkg.org :

Pour résumer les mesures préventives à prendre face à ce risque potentiel :
Utilisez
lockfile-lintdans votre environnement de développement et votre CI.N’autorisez pas les contributeurs externes à modifier le fichier de verrouillage.
Utilisez des bots automatisés de mise à niveau des dépendances, qui mettent à jour le fichier de verrouillage de manière automatisée et fiable (ils récupèrent les packages depuis le registre officiel). À noter : le bot Snyk fait cela.
(2) Prévenir l’exécution de commandes arbitraires
Vous savez probablement déjà que l’exécution de la commande npm install peut permettre aux packages installés d’exécuter des commandes arbitraires pendant leur installation.
Si, à un moment donné, vous aviez exécuté la commande suivante :
Vous auriez été victime du package npm node-ipc, détourné à des fins malveillantes : son mainteneur l’a modifié pour exécuter des commandes qui créent des fichiers sur le bureau. Dans d’autres versions du package npm node-ipc, il a choisi une approche plus destructrice, notamment en parcourant les fichiers de votre disque dur pour les effacer entièrement.
Si les dépendances peuvent exécuter des commandes arbitraires, c’est parce que le gestionnaire de packages npm fournit un hook de cycle de vie install dans le cadre du processus d’installation d’une dépendance. La plupart du temps, les mainteneurs de modules l’utilisent à des fins légitimes, par exemple pour préparer les artefacts d’installation, récupérer des fichiers de ressources ou effectuer un nettoyage après l’installation. C’est aussi un moyen efficace de réaliser une compilation croisée pour les liaisons de modules natifs, qui doivent être compilées sur l’hôte où elles sont installées.
L’étude précédente, intitulée Quels sont les maillons faibles de la chaîne d’approvisionnement npm ?, à laquelle nous faisions référence, apporte des éclairages sur l’utilisation des hooks d’installation dans l’écosystème :
Nous avons constaté que 2,2 % (33 249) des packages utilisent des scripts d’installation, ce qui indique que 97,8 % des packages pourraient suivre la recommandation de npm de ne pas utiliser de script d’installation, considérée comme une bonne pratique de sécurité.
Voici les mesures préventives à prendre :
Ajoutez l’option
--ignore-scriptsà votre commandenpm installpour empêcher l’exécution de commandes arbitraires par les dépendances des packages npm.Pensez à ajouter cette option au fichier de configuration
.npmrcde votre projet afin de protéger également les autres développeurs qui y travaillent avec vous.Sachez que l’activation de cette option peut empêcher l’exécution de certains cas d’usage légitimes des packages npm et interrompre le processus npm install.
Notez que l’option
--ignore-scriptsne s’applique pas aux packages installés à partir de sources de fichiers locales ni à ceux installés depuis un dépôt Git.
(3) Éviter les mises à niveau aveugles des packages NPM
Certains développeurs peuvent explicitement mettre à niveau toutes leurs dépendances vers les dernières versions dans le cadre d’un processus d’intégration continue (CI). Ils procèdent ainsi, tout en exécutant des tests et des cas de test du parcours nominal, afin de s’assurer que leurs applications fonctionnent toujours comme prévu lorsque de nouvelles versions de leurs dépendances sont publiées. C’est un moyen de garantir la compatibilité future sans provoquer de régression dans l’application.
Pour installer la dernière version npm, vous pouvez exécuter :
Ou utiliser le package npm dédié :
Même si ces mises à niveau répondent à des besoins légitimes, ces mises à niveau aveugles des packages npm (blind npm package upgrades) exposent aussi à de potentiels incidents de sécurité. Comme nous venons de le voir, exécuter npm install comporte de sérieux risques.
La mise à niveau aveugle de vos dépendances vous expose inutilement à des risques de sécurité, comme les attaques par confusion de dépendances et d’autres incidents survenus ces dernières années, tels que l’incident de sécurité lié à colors ou l’incident de sécurité lié à node-ipc.
Voici les mesures préventives à prendre pour maintenir vos dépendances à jour :
Utilisez un outil intelligent de mise à niveau automatisée des dépendances. Ces outils intègrent souvent une fonctionnalité de mise à jour automatique des manifestes et des fichiers de verrouillage.
Examinez attentivement le journal des modifications et les artefacts de publication. Vérifiez que le mainteneur n’a pas changé ou que ce changement est justifié. Assurez-vous également que le code source correspondant à la nouvelle version publiée est disponible.
Il est particulièrement important de signaler les mises à niveau automatisées et le risque d’installer un paquet npm malveillant. Snyk recommande de ne pas passer à des versions publiées depuis moins de 21 jours. Vous éviterez ainsi les versions qui introduisent des bugs fonctionnels et sont ensuite retirées, ou celles publiées depuis un compte compromis (dont le propriétaire a perdu le contrôle au profit d’une personne malveillante).
Pour en savoir plus, consultez la documentation Snyk sur la mise à niveau des dépendances avec les pull requests automatisées.
(4) Prévenir la confusion des dépendances
La confusion des dépendances est un type d’attaque dans lequel un paquet est créé et utilisé en interne par une organisation, sur un proxy privé ou un service d’hébergement, mais où le nom de ce paquet reste libre à l’enregistrement sur le registre npm.
Malheureusement, il est facile de mal configurer les outils locaux. Associé au fonctionnement du gestionnaire de paquets npm, cela crée la situation suivante : lorsqu’un paquet portant le même nom existe sur le registre npm public et qu’une version plus récente que celle installée est disponible, npm l’installe.
Si un employé de Microsoft n’avait pas été vigilant, ou si son gestionnaire de paquets npm et son serveur proxy interne avaient été mal configurés, cela aurait pu entraîner une attaque par confusion des dépendances, capable de s’infiltrer dans l’infrastructure interne de l’entreprise :
En fait, le même risque s’applique si des employés exécutent cette commande approuvée de mise à jour des paquets npm :
Cela s’explique par le fonctionnement des attaques par confusion des dépendances. Bien que cette méthode ait fait l’objet de recherches publiques en 2021, l’équipe de recherche en sécurité de Snyk a trouvé des preuves que des acteurs malveillants ciblent l’infrastructure d’entreprises de cette manière. Par ailleurs, Nishant Jaint, chercheur en sécurité, a décrit un autre vecteur d’attaque potentiel : la confusion des dépendances à l’aide d’alias de paquets npm.
Pour détecter et prévenir les attaques par confusion des dépendances, vous pouvez utiliser snync, un outil gratuit et open source de Snyk. Voici un exemple d’exécution :
(5) Sécurité npm : une protection proactive contre les logiciels malveillants
Vous avez probablement déjà exécuté une commande npm install pour installer un paquet npm, avant de voir s’afficher un résultat semblable à celui-ci :
Le problème, c’est que si des vulnérabilités de sécurité ont été détectées dans les paquets, vous n’en avez été informé qu’après les avoir installés. Que contient le paquet npm amp-html ? Avant d’aborder ce sujet, voyons comment mieux installer des paquets et reprendre le contrôle.
Imaginez pouvoir recueillir des informations utiles sur l’état de vos dépendances avant de les installer, puis décider en connaissance de cause d’ajouter ou non ce paquet npm à votre projet, ou de « continuer vos recherches » sur npmjs.org.
C’est là que je souhaite vous présenter un de mes projets open source, npq. Avec npq, vous pouvez mettre en place des mesures de sécurité proactives et décider en connaissance de cause d’installer ou non un paquet, selon des critères tels que la présence d’un fichier README, d’une licence conforme ou d’un dépôt GitHub associé au projet.
L’outil npq est même entièrement intégré à Snyk : si vous créez un compte gratuit, il détectera le jeton d’API Snyk disponible dans votre environnement et interrogera la Snyk Vulnerability Database.
Dans mon environnement de développement, j’ai créé un alias qui associe la commande npm à npq, comme suit :
Désormais, chaque fois que j’exécute npm install, la commande appelle en réalité npq pour effectuer ses vérifications au préalable. Si je décide de continuer, npq passe alors le relais à l’interface en ligne de commande du gestionnaire de paquets npm pour poursuivre l’installation :

Vous pouvez également installer npq et l’utiliser ponctuellement pour rechercher des vulnérabilités :
Vous pouvez aussi évaluer l’état des dépendances d’un paquet npm avec Snyk Advisor, un outil pratique pour rechercher des paquets npm, des solutions de remplacement ou des paquets similaires, et en savoir plus sur leur maintenance et leur sécurité.
Voici comment le paquet npm amp-html apparaît dans Snyk Advisor :

(6) Attaques Trojan Source
Les attaques Trojan Source sont le sujet d’un article de recherche en sécurité récemment publié, qui s’intéresse aux vulnérabilités du code source invisibles.
Voici un extrait de code tiré d’un projet Node.js backend, qui gère une partie des accès administrateur provisionnés à une route de page spécifique. Il contient en réalité une attaque Trojan Source. Saurez-vous repérer le problème ?
Je vais activer la coloration syntaxique JavaScript dans mon IDE pour vous faciliter la tâche. Voici donc une capture d’écran de l’extrait ci-dessus :

Concentrez-vous sur la deuxième condition, liée à Debug login for issue #1812. Pour mieux comprendre ce qui se passe, transposons ce code dans un programme Node.js exécutable :
Si vous exécutez ce code avec node test.js, le résultat suivant s’affichera :
Mais pourquoi ? Vous vous grattez déjà la tête, je suppose.
La raison tient au fait que l’extrait de code source ci-dessus contient des caractères de contrôle invisibles, mais qui modifient profondément le texte réel du code source. Ces caractères inversent l’ordre du texte, de gauche à droite et de droite à gauche. Ainsi, lorsqu’un interpréteur ou un compilateur lit le code, il en perçoit un sens différent de celui que l’œil humain lit sans s’en rendre compte.
Comment les développeurs peuvent-ils repérer ce type de problème et ne pas le laisser passer lors des revues de code soumis à leur projet ?
Si vous utilisez Snyk Code pour surveiller votre code source, il vous signalera les vulnérabilités de sécurité détectées, comme les injections SQL ou les injections de code non sécurisées. De plus, Snyk Code signale également les problèmes liés aux attaques Trojan Source dans le code :

Snyk vous fournit également un aperçu visuel rapide de la vulnérabilité Trojan Source, avec les autres vulnérabilités détectées dans votre projet, sur la page de présentation du projet :

Heureusement, Visual Studio Code et la plateforme GitHub ont tous deux été mis à jour : des repères visuels vous avertissent lorsque les fichiers de code source que vous consultez contiennent des caractères de contrôle invisibles.
Un autre outil open source qui aide à détecter et atténuer les attaques Trojan Source dans les bases de code JavaScript avec ESLint est le plugin ESLint eslint-plugin-anti-trojan-source.
Sécurité npm : quelle suite ?
Les risques liés à la sécurité de la chaîne logistique logicielle ne cessent d’augmenter. Et comme si cela ne suffisait pas, les attaques sont de plus en plus ciblées contre les développeurs et leurs écosystèmes.
Je vous recommande vivement de lire les articles suivants pour approfondir les bonnes pratiques de sécurité npm et les sujets connexes :
Prévenir les paquets malveillants et les attaques contre la chaîne logistique avec Snyk, par Daniel Berman.
Qu’est-ce qu’une porte dérobée ? Créons-en une avec Node.js, par Ulises Gascón
10 bonnes pratiques de sécurité npm, par Liran Tal et Juan Picado.
Si vous souhaitez vous former aux pratiques de programmation sécurisée, je vous recommande également de suivre l’un des cours pratiques sur la sécurité JavaScript proposés par Snyk Learn. Ils sont courts et entièrement gratuits !
Les paquets NPM peuvent-ils être malveillants ?
La triste réalité, c’est que les paquets npm peuvent effectivement être malveillants et recourir à différentes techniques, comme les attaques par typosquattage ou l’ingénierie sociale pour introduire une porte dérobée dans event-stream, afin d’injecter du code malveillant qui s’exécute dans l’environnement de développement. En fait, certains paquets malveillants sont le fait de mainteneurs qui ont saboté leurs propres paquets npm en y intégrant du code malveillant, comme dans le cas de node-ipc.
Comment vérifier la sécurité des paquets NPM ?
Snyk détecte régulièrement des paquets npm malveillants et les signale, comme lors de la découverte de plus de 200 paquets npm malveillants. Pour vérifier la sécurité des paquets npm, vous pouvez utiliser Snyk Advisor afin d’évaluer l’état des paquets open source, ou utiliser Snyk CLI et l’intégration aux dépôts (gratuites) pour analyser les paquets malveillants et surveiller leur présence.
Quelles sont les conséquences des vulnérabilités NPM ?
Les vulnérabilités de sécurité présentes dans les paquets npm peuvent affecter votre application et vous exposer à des risques considérables. Si vous utilisez une version non corrigée du paquet npm websocket-extensions, vous êtes vulnérable aux attaques par déni de service via des expressions régulières. De même, si vous utilisez une version non corrigée du paquet npm st pour héberger et servir des fichiers statiques avec votre application Node.js, vous êtes vulnérable aux attaques par traversée de répertoires.
Apprécié par les développeurs. Les équipes de sécurité lui font confiance.
Les outils Snyk, conçus pour les développeurs, offrent une sécurité intégrée et automatisée qui répond à vos exigences de gouvernance et de conformité.



