Comment empêcher la publication de paquets malveillants
27 mars 2016
0 minutes de lectureLa semaine dernière, le CERT a alerté les utilisateurs sur le risque de publier ou de consommer un paquet npm malveillant. Bien que ce risque ne soit pas propre à npm, il est plus susceptible de se concrétiser dans cet écosystème. Il convient de noter que, même s’il s’agit bien d’un vecteur d’attaque, il ne s’agit pas, à mon avis, d’une vulnérabilité, mais plutôt d’un paramètre par défaut non sécurisé.
Cet article explique le risque et comment vous en protéger. Nous examinerons les compromis à l’origine du problème, étudierons un scénario d’exploitation et vous indiquerons comment atténuer le risque dans vos propres environnements. Si vous souhaitez uniquement connaître les mesures à prendre, passez directement aux sections consacrées à l’atténuation.
Vous pouvez également consulter 10 bonnes pratiques de sécurité pour npm, où vous trouverez un aide-mémoire PDF téléchargeable ainsi qu’un article détaillé sur l’adoption des consignes de sécurité et des pratiques sécurisées pour npm.
L’attaque
Les paquets malveillants en 5 étapes faciles
Le problème tient avant tout à la facilité avec laquelle on peut publier un nouveau paquet. Comme git, pip et d’autres outils, npm permet aux utilisateurs d’enregistrer leurs identifiants dans un fichier point (.npmrc), puis de publier sans invite interactive.
Malheureusement, c’est un cas où la simplicité peut aller trop loin. Le processus de publication simplifié augmente le risque de publier par erreur, mais surtout, il ouvre la voie aux attaquants qui souhaitent publier du code malveillant. Pour publier une version malveillante d’un paquet, l’attaquant doit simplement :
Exécuter du code sur la machine du développeur.
Déterminer quel utilisateur npm est connecté (
npm whoami --silent)Télécharger l’un des paquets de l’utilisateur dans un dossier temporaire
Modifier package.json : ajouter un hook
postinstallmalveillant et incrémenter la version de 0.0.1Publier (
npm publish)
Parmi ces étapes, seule la première est difficile. Malheureusement, la plupart des développeurs ont tendance à utiliser leurs environnements avec assez peu de précautions…
Vous est-il déjà arrivé d’installer quelque chose avec curl X | bash ? C’est une façon pour un attaquant de s’introduire dans votre système : il n’a même pas besoin de sudo bash. Ou peut-être installez-vous parfois un paquet npm localement, juste pour l’essayer ? N’importe laquelle de ses dépendances peut exécuter un hook postinstall qui réalise les opérations décrites ci-dessus.
En réalité, nous, les développeurs, expérimentons, ce qui signifie naturellement que nous n’utilisons pas des environnements irréprochables. Ces environnements ne devraient pas disposer de droits de publication.
Propagation rapide, détection lente
Une fois l’attaque menée à bien, l’attaquant n’a plus qu’à attendre que son code se propage dans l’écosystème Node. La plupart des consommateurs du paquet utilisent une plage semver qui inclut implicitement les versions de correctif (0.0.X) et récupèrent cette nouvelle version sans intervention. Ils peuvent également faire en sorte que le code malveillant se propage aux paquets appartenant à ses victimes, créant ainsi un ver qui se propage encore plus rapidement.
Pour ne rien arranger, si un paquet malveillant de ce type a été publié, il n’est pas facile de s’en apercevoir. npm n’avertit pas l’utilisateur lorsqu’une nouvelle version est publiée, et il n’existe aucun moyen simple de voir les différences entre les paquets (contrairement à git). Le meilleur moyen de repérer ce genre de problème consiste à remarquer le nouveau numéro de version. Si vous maintenez activement un projet développé par une seule personne, vous remarquerez probablement les changements de version. En revanche, pour les projets inactifs ou développés par une équipe entière, ils peuvent facilement passer inaperçus.
Mesures d’atténuation
Comment empêcher la publication malveillante (paquets publics)
Si vous collaborez à un paquet npm, il vous incombe de vous protéger. Ne laissez pas des attaquants publier une version malveillante en votre nom. C’est simple : déconnectez-vous.
Limitez-vous à la publication via votre CI lorsqu’une compilation de la branche master réussit, en utilisant semantic-release ou des scripts personnalisés. Voici la marche à suivre :
Révoquez tous les jetons actifs. Vous pouvez le faire depuis la page de gestion des jetons. Demandez également à tous les collaborateurs de faire de même.
Configurez un nouveau jeton dans votre CI. Remy a rédigé des instructions détaillées pour Travis, mais la plupart des systèmes CI prennent en charge les variables masquées. Notez qu’une fois votre jeton configuré, l’exécution de
npm logoutl’invalidera. Supprimez plutôt votre fichier~/.npmrcpour vous déconnecter.Pour les versions de développement, utilisez git. Si les utilisateurs doivent accéder à une version temporaire du paquet (par exemple, pour résoudre un problème), validez-la dans une branche et indiquez-leur comment l’installer à partir d’un commit git.
Comment empêcher la publication malveillante (paquets privés)
Si vous utilisez un paquet privé, vous ne pouvez pas vous déconnecter, car vous devez être connecté pour accéder à vos paquets privés. Heureusement, npm a récemment lancé les organisations et permet de créer des utilisateurs en lecture seule, ce qui vous donne accès aux paquets sans risque de publication.
Voici à nouveau la marche à suivre :
Créez une organisation npm. Si vous êtes le seul utilisateur, cela augmentera le coût minimal de 7 $ par mois, mais l’investissement en vaut largement la peine.
Créez une équipe disposant d’un accès en lecture seule à vos paquets ou à votre espace de noms.
Ajoutez des utilisateurs à cette équipe en tant que membres. Vous pouvez également réutiliser un seul utilisateur en lecture seule.
Demandez à votre équipe (et à vous-même) de se connecter avec ces utilisateurs en lecture seule.
Ces mesures ne vous dispensent pas de faire preuve de prudence lorsque vous installez des logiciels inconnus sur votre ordinateur, mais elles réduisent au moins le risque que vous serviez de vecteur de propagation pour du code malveillant.
Comment vous protéger des paquets malveillants
En tant qu’utilisateur de paquets npm, vous ne pouvez pas éliminer complètement ce risque (cela vaut également pour les autres gestionnaires de paquets). La seule façon de l’atténuer consiste à contrôler les paquets que vous installez.
Si vous développez en local, pensez à utiliser npm shrinkwrap pour verrouiller les versions des paquets que vous utilisez. shrinkwrap fige ces versions et n’en adopte pas de nouvelles, sauf si vous le demandez explicitement. Vous pouvez ainsi examiner manuellement chaque mise à jour. C’est contraignant, mais cela renforcera votre sécurité.
Si vous êtes une organisation, envisagez d’utiliser npm On-Site et de créer une liste d’autorisation pour les paquets que vous acceptez. Là encore, cela demande quelques efforts, mais réduit le risque d’introduction de paquets malveillants. Pour être parfaitement protégé, veillez à définir Read through cache sur Off.
Ces deux solutions ralentissent l’adoption des nouvelles versions et peuvent donc vous exposer aux vulnérabilités découvertes dans les anciennes versions de ces paquets. Si vous optez pour l’une d’elles, je vous recommande de suivre de près les vulnérabilités connues dans vos dépendances npm, par exemple en utilisant Snyk.
npm ne peut-il pas simplement résoudre le problème ?
Ce ne serait pas formidable si npm pouvait simplement ajouter une ou deux fonctionnalités pour éliminer complètement ce risque ?
Malheureusement, ce n’est pas possible. Ce risque découle naturellement de l’utilisation de paquets open source. Nous utilisons de nombreux petits fragments de code écrits par différentes personnes. Cette contribution collective est formidable pour la productivité, mais elle signifie que nous dépendons fortement des auteurs de ces paquets et de leurs intentions.
Il est également important de noter que ces comportements ne sont pas propres à npm. La plupart des autres gestionnaires de paquets permettent de publier sans invite, et pratiquement tous autorisent l’exécution de code personnalisé lors de l’installation d’un paquet. Si ce risque est plus élevé avec npm, c’est uniquement parce que le nombre de dépendances indirectes utilisées est supérieur, ce qui rend leur suivi manuel pratiquement impossible.
Cela dit, j’aimerais que l’équipe npm prenne quelques mesures pour améliorer la situation :
Envoyer un e-mail à tous les collaborateurs lorsqu’une nouvelle version d’un paquet est publiée. Ce devrait être le comportement par défaut, avec la possibilité pour les utilisateurs de désactiver les notifications par la suite.
Exiger des identifiants pour exécuter
npm publish, sauf en cas d’utilisation d’un jeton d’automatisation explicitement généré. Le comportement par défaut (utilisernpm login) serait ainsi plus sûr. Il s’agit probablement d’un changement incompatible ; je ne m’attendrais donc pas à ce qu’il soit adopté avant npm 4.Dans la mesure du possible, limiter les actions que les scripts d’installation peuvent effectuer pendant l’installation. Cela n’éliminerait pas le risque, mais rendrait l’attaque plus difficile et donc moins probable.
Lancez-vous dans les compétitions Capture The Flag
Apprenez à résoudre des défis de capture du drapeau en regardant à la demande notre atelier virtuel d’initiation.