Skip to main content

Packages Node.js malveillants, visibles et invisibles : quand des configurations méconnues rencontrent des caractères invisibles

Écrit par
Headshot of Aviad Hahami

Aviad Hahami

blog npmyarn feature

28 février 2022

0 minutes de lecture

Ces dernières années, nous avons constaté une forte augmentation du nombre de packages open source créés et utilisés à grande échelle. Aujourd’hui, chaque écosystème a son gestionnaire de packages, et presque tous les gestionnaires de packages recèlent leurs joyaux cachés et leurs configurations méconnues.

Cela dit, à mesure que les développeurs installent continuellement un nombre toujours croissant de packages, les attaquants s’intéressent davantage à leurs surfaces d’attaque. C’est alors que commence la quête du package malveillant parfaitement dissimulé.

Cet article porte sur les attaques exploitant l’écosystème Node.js et les gestionnaires de packages yarn et npm. Mais voyez-le comme un « appel à l’action » : examinons les autres écosystèmes à la recherche de problèmes similaires, afin de sensibiliser davantage et d’améliorer la sécurité globale de notre communauté.

Ceci étant dit, entrons dans le vif du sujet !

Partie 1 : configurations spécifiques

L’autre jour, un chercheur avec qui je travaille m’a parlé de quelques options de configuration intéressantes de npm, peu connues et susceptibles d’avoir de graves conséquences sur la sécurité. (Nous en avons ensuite trouvé de similaires dans yarn.) Ces options permettent d’indiquer au gestionnaire de packages quel binaire exécuter dans le répertoire du projet. Oui, quel binaire !

Côté configuration, ces options sont yarnPath dans le fichier .yarnrc, ainsi que git, shell et script-shell dans le fichier .npmrc, pour n’en citer que quelques-unes. Pour bien comprendre le vecteur d’attaque décrit ci-dessous, je vous recommande vivement de lire l’article de Rotem (de Cider Security) sur cette découverte.

On peut se demander : « Pourquoi ces fichiers (que je viens de cloner/télécharger) seraient-ils pris en compte ? » La réponse, c’est que c’est ainsi que fonctionnent npm et yarn. Dans le cadre du traitement d’un projet, le gestionnaire de packages recherche les fichiers .rc selon une hiérarchie : d’abord dans le répertoire du projet courant, puis dans le répertoire utilisateur et enfin dans le répertoire racine (vous trouverez plus d’informations dans la documentation sur la configuration de NPM).

Avec les options ci-dessus, nous pouvons donc créer un package malveillant relativement bien dissimulé (dans des dépôts ou des archives tar, car les fichiers `.rc` ne sont pas téléchargés lors de l’exécution de `npm install` ou d’une commande équivalente), en utilisant le fichier .yarnrc suivant comme configuration :

# .yarnrc content

yarnPath: “./evil.sh”

(Remarque : cet exemple utilise un fichier .yarnrc, mais un fichier .npmrc similaire — configuré avec l’option appropriée — produira le même résultat.)

Ainsi, toute exécution de yarn install à la racine du projet ci-dessus lancera evil.sh. Il ne nous reste plus qu’à ajouter le fichier malveillant.

Deux remarques importantes :

  1. Même si vous fournissez l’option --ignore-scripts ou une instruction équivalente, l’action décrite ci-dessus aura tout de même lieu.

  2. Si un tel package est une dépendance de votre projet, le fichier .yarnrc malveillant ne sera pas pris en compte, car il se trouve dans node_modules, et non au même niveau. Il n’y a donc pas de risque.

Partie 2 : dissimuler du code à la vue de tous

Le code source cheval de Troie a également beaucoup fait parler de lui l’année dernière : il s’agit de code malveillant utilisant des caractères dont les valeurs Unicode diffèrent de leur apparence habituelle.

L’idée est assez simple : certains caractères (autres que l’espace, CR, LF, etc.) s’affichent comme des espaces blancs tout en constituant des chaînes valides.

Pour approfondir un peu, le caractère apparemment vide « … » (0x3164 en hexadécimal) s’appelle « HANGUL FILLER » et, comme vous pouvez (ne pas) le voir, ce n’est pas un espace, mais une « lettre » valide. Nous pouvons exploiter cette particularité pour dissimuler une logique précise dans des extraits de code et passer inaperçus en introduisant du code malveillant.

Les informations ci-dessus suffisent pour notre démonstration ; je ne m’étendrai donc pas davantage sur le sujet. Pour en savoir plus et découvrir le contexte et des exemples, je vous recommande vivement les ressources suivantes :

Partie 3 : binaires cachés + configurations cachées = RCE

Maintenant que ces éléments sont posés, les prochaines étapes de notre attaque sont simples :

  1. Créer un package qui semble inoffensif.

  2. Créer un fichier malveillant (dont le nom contient un caractère invisible).

  3. Créer un fichier .rc.

  4. Configurer le paramètre pour qu’il pointe vers le nom du binaire contenant le caractère invisible.

Une fois ces étapes terminées, nous sommes prêts. Voyons cette attaque du point de vue des victimes :

Un développeur qui découvre un tel package sans connaître ce vecteur d’attaque ne remarquera probablement pas les signaux d’alerte.

Peut-être est-il un peu prudent et exécute ls -l. Il verra alors :

$ ls -l
total 24
-rw-r--r--  1 root  staff   27 Nov 16 13:48 index.js
-rw-r--r--  1 root  staff  295 Nov 16 17:47 package.json
-rw-r--r--  1 root  staff  354 Nov 16 17:47 yarn.lock

Comme le résultat n’a rien d’inhabituel, c’est le premier piège. La plupart des cibles exécuteront yarn install et en seront victimes.

Supposons toutefois que notre cible soit un développeur chevronné, qui ajoute à ls. l’option « afficher les caractères invisibles ». Le résultat sera :

$ ls -la
total 32
drwxr-xr-x  7 root  staff  224 Feb 14 16:18 .
drwxr-xr-x  6 root  staff  192 Nov 25 17:48 ..
-rw-r--r--  1 root  staff  109 Nov 16 15:27 .yarnrc
-rw-r--r--  1 root  staff   27 Nov 16 13:48 index.js
-rw-r--r--  1 root  staff  295 Nov 16 17:47 package.json
-rw-r--r--  1 root  staff  354 Nov 16 17:47 yarn.lock
-rwxr-xr-x  1 root  staff  197 Feb 14 16:28

La cible pourrait alors se demander : « D’accord, que contient le fichier .rc ? » (sans remarquer le fichier discret et invisible), puis l’afficher avec cat. Voici le résultat :

$ cat .yarnrc

yarn-path "./"

C’est le deuxième piège.

La plupart des développeurs ne savent pas que yarn (ou npm) peut lancer des binaires personnalisés. Ce qui précède est étrange, certes, mais ça ne dit pas « evil.sh », n’est-ce pas ?

Le troisième piège est assez simple : la cible ne voit tout simplement pas le fichier. Même si elle remarque le fichier .rc et consulte la spécification yarn-path (npm propose aussi la même chose), elle peut supposer que la propriété ne pointe vers rien et lancer le gestionnaire de packages malgré tout.

Dans ma preuve de concept (PoC), l’exécution de yarn install donne le résultat suivant :

$ yarn install
Okayyyy lesgo!

Mesures d’atténuation et réflexions

Pour vous protéger, vous pouvez prendre les mesures suivantes :

  1. Ne faites jamais confiance au code (de tiers). Assurez-vous toujours de savoir ce que vous avez cloné ou téléchargé, et méfiez-vous des comportements et des configurations inhabituels.

  2. Exécutez le code dans un bac à sable. Lancez toujours le code de tiers dans un environnement isolé ou conteneurisé. Ainsi, même si le package est malveillant, il n’affectera probablement pas votre système et ne compromettra pas votre machine.

    Remarque : l’exécution dans un bac à sable n’est pas non plus sûre à 100 % : il existe des vulnérabilités du noyau, des évasions de bac à sable, etc. Toutefois, utiliser un tel environnement réduit considérablement les risques et bloque la plupart des attaques simples (comme un shell inversé basique ou une attaque équivalente).

Et voilà, l’attaque est neutralisée.

Ce qui est fascinant dans ce vecteur d’attaque, c’est qu’il est parfaitement visible et pourtant invisible.

On pourrait dire : « Je lance toujours ls -la, je vérifie tous les fichiers et je connais parfaitement toutes les options de configuration des gestionnaires de packages. » D’accord, mais est-ce vraiment le cas ? Et, plus généralement, est-ce vrai pour tout le monde dans votre organisation ?

Et comme pour les filtres de Bloom, la réponse est ici : « non signifie non, oui signifie peut-être ».

Pour conclure, je tiens à rappeler qu’il s’agit d’un appel à l’action — et non à l’exploitation — destiné à sensibiliser la communauté à ces vecteurs d’attaque. Alors que tout le monde développe des applications et installe presque aveuglément des dépendances, il est essentiel de signaler aux autres les configurations suspectes.

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.

Lire la suite

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.

feature insights context
Blog

Les attaques autonomes sont déjà là. La défense doit suivre leur rythme.

Les attaquants autonomes réduisent la fenêtre de défense. Découvrez comment la découverte, la correction, la validation et la prévention continues peuvent aider les équipes de sécurité à suivre le rythme.

Blog

Pourquoi les agents de codage IA créent-ils sans cesse des failles de contrôle d’accès ?

Les agents de codage IA peuvent générer une logique d’autorisation qui compile et passe la revue, tout en exposant les données d’un tenant à un autre. Découvrez pourquoi les failles de contrôle d’accès sont difficiles à détecter et comment les prévenir.