Skip to main content

Exploiter les paramètres par défaut non sécurisés de npm pour dérober les raccourcis clavier de macOS

Écrit par
feature open source

28 juin 2023

0 minutes de lecture

Les paquets npm malveillants et les dangers qu’ils représentent sont souvent évoqués : qu’il s’agisse de centaines de paquets malveillants Cobalt Strike de commande et de contrôle, de typosquattage ou de logiciels malveillants publiés sur le registre npm (ainsi que sur PyPI et d’autres registres). Pour aider les développeurs et les responsables de la maintenance à se protéger contre ces risques de sécurité, Snyk a publié un guide sur les bonnes pratiques de sécurité pour npm.

Cela étant dit, l’ampleur de l’attaque décrite ci-dessous, dont Yagiz Nizipli a averti des responsables de la maintenance de longue date, et le risque réel de compromission des données illustrent parfaitement l’importance de réduire les risques d’exécution de commandes arbitraires avec les gestionnaires de paquets, notamment via les hooks de cycle de vie postinstall de npm.

Scripts de cycle de vie de npm

Node Package Manager (npm) fournit aux développeurs et aux responsables de la maintenance des paquets un ensemble de scripts permettant de gérer les événements du cycle de vie d’un paquet. Ces scripts sont très utiles aux développeurs, car ils leur permettent d’effectuer différentes tâches ou configurations lors de l’installation d’un paquet. Par exemple, avec les scripts postinstall, les développeurs peuvent automatiser des tâches comme la compilation des ressources, la configuration des variables d’environnement, l’exécution de migrations ou toute autre tâche automatisable.

La propriété scripts d’un fichier package.json définit les commandes déclenchées par le cycle de vie du paquet et par les dépendances du paquet que vous développez. À ce jour, npm prend en charge un nombre limité de scripts de cycle de vie dans toute propriété scripts d’un fichier package.json.

Par souci de simplicité, la suite de cet article se concentre sur la commande postinstall. Toutefois, tous les concepts présentés s’appliquent également aux autres opérations du cycle de vie.

Incidents de sécurité passés

Plusieurs incidents très médiatisés ont eu des conséquences réelles pour les développeurs JavaScript, notamment :

De nombreux autres incidents de sécurité liés à JavaScript et Node.js sont répertoriés dans le dépôt Awesome Node.js Security.

Sécurité des données au repos

Les spécialistes de la sécurité parlent de protection des données Data-at-rest lorsqu’elles sont stockées, par opposition aux données en transit ou en cours de traitement. Cette notion concerne la protection des informations sensibles stockées dans des bases de données, des systèmes de fichiers ou un stockage persistant. La sécurité des données au repos vise à empêcher tout accès non autorisé, toute divulgation ou toute altération des données lorsqu’elles sont inactives. Différentes mesures permettent de garantir cette sécurité, notamment :

  • Déchiffrement à la demande : déchiffrer uniquement les données nécessaires à la tâche en cours et conserver le reste sous forme chiffrée afin d’empêcher tout accès non autorisé.

  • Logique de contrôle d’accès : vérifier l’identité du demandeur à l’aide d’un mécanisme tel qu’un mot de passe, une authentification à deux facteurs ou la biométrie fournie par un système d’exploitation (comme Face ID), afin de limiter l’exposition de la ressource aux personnes non autorisées.

La surface d’attaque d’un développeur

Les bonnes pratiques du secteur nous imposent de suivre certains principes pour développer des applications. Elles présentent de nombreux avantages lorsque l’on travaille avec différentes équipes et différents développeurs, mais elles élargissent également la surface d’attaque.

Quelles données se trouvent sur le poste d’un développeur sans être chiffrées ?

  • Les variables d’environnement enregistrées dans des fichiers texte brut, comme .env (accessibles via le paquet dotenv).

  • Les fichiers de configuration des projets stockés au format JSON, comme config.json.

  • Les clés SSH permettant d’accéder à Github/Gitlab, disponibles dans le dossier ~/.ssh.

  • Et… les raccourcis clavier de macOS !

Remplacements de texte dans macOS

Par défaut, macOS propose une fonctionnalité appelée Text Replacements, dissimulée dans l’application Préférences Système. Elle permet de remplacer rapidement un mot par un autre. J’ai récemment appris qu’un développeur d’une entreprise connue utilisait les remplacements de texte pour substituer le mot-clé @card aux informations de sa carte bancaire. Même si le numéro de carte, sans sa date d’expiration ni son cryptogramme visuel, ne permet pas à lui seul de dérober votre argent, il élargit la surface d’attaque.

Réglages du clavier macOS affichant le remplacement de texte « @ssh-key » par « my-secret-password »
La fonctionnalité Remplacements de texte est accessible via l’application Préférences Système, sous l’élément de menu `Clavier`.

Exfiltration des remplacements de texte du clavier 

Les raccourcis clavier sont enregistrés dans defaults, qui correspond à un fichier .plist adossé au système de fichiers, quelque part dans votre dossier local. L’exécution de la commande suivante renvoie les remplacements de texte configurés, également accessibles depuis l’application Préférences Système.

Notez que le code suivant ne nécessite pas d’accès sudo et peut être exécuté par n’importe quel processus sur votre ordinateur.

> defaults read -g NSUserDictionaryReplacementItems
(
  {
    on = 1;
    replace = "@ssh-key";
    with = "my-secret-password";
  }
)

La même commande peut être exécutée avec execSync dans Node.js, puis analysée sans difficulté via l’opération de cycle de vie postinstall prise en charge par le gestionnaire de paquets npm.

Voici un exemple de script Node.js que des acteurs malveillants peuvent utiliser pour accéder aux remplacements de texte de macOS et exfiltrer des données sensibles :

import { execSync } from 'node:child_process'

const decoder = new TextDecoder()
const res = execSync('defaults read -g NSUserDictionaryReplacementItems')
const text_replacements = decoder.decode(res)

console.log(text_replacements)

Pour que le code ci-dessus s’exécute lors de l’installation du paquet, nous allons modifier le fichier manifeste du paquet, package.json, comme suit :

{
  "name": "my-useful-library",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "type": "module",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "postinstall": "node ./retrieve.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

Distribuée via npm et téléchargée par un développeur, cette bibliothèque exécutera directement notre script personnalisé pour récupérer et traiter les remplacements du clavier. Si vous n’y prenez pas garde, il est facile de ne pas voir la ligne contenant > node ./retrieve.js.

➜  vulnerable npm i

> my-useful-library@1.0.0 postinstall
> node ./retrieve.js

up to date, audited 1 package in 192ms

found 0 vulnerabilities
➜  vulnerable

Protection

En tant que développeur, que pouvez-vous faire pour limiter les risques de sécurité liés aux paquets npm malveillants et les risques généraux d’exécution de commandes arbitraires par des paquets de votre arbre de dépendances ?

Ignorer les scripts lors de l’installation de paquets npm

Vous pouvez vous protéger contre les paquets qui utilisent des scripts postinstall. npm propose l’option de configuration --ignore-scripts lors de l’installation de paquets.

➜  npm i <package name> --ignore-scripts
up to date, audited 1 package in 124ms
found 0 vulnerabilities

Utiliser des paramètres par défaut sûrs pour npm

Le gestionnaire de paquets npm dispose également d’un fichier de configuration appelé .npmrc. Vous pouvez modifier les préférences par défaut à l’aide de l’interface en ligne de commande npm afin de garantir des paramètres sécurisés :

➜  npm config set ignore-scripts true
➜  npm i
up to date, audited 1 package in 126ms
found 0 vulnerabilities

Stockage sécurisé

Surtout, ne stockez jamais d’informations sensibles en texte brut. Si d’autres exigences vous obligent à les stocker ainsi, veillez à toujours protéger l’accès à la ressource par une authentification multifacteur.

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.