Autopsie de la porte dérobée malveillante dans event-stream
6 décembre 2018
0 minutes de lectureLa semaine dernière, l’impensable s’est produit. Un paquet malveillant, flatmap-stream, a été publié sur npm, puis ajouté comme dépendance au paquet event-stream, largement utilisé, par l’utilisateur right9ctrl. Quelque temps et 8 millions de téléchargements plus tard, des applications partout sur le Web exécutaient à leur insu du code malveillant en production. Nous avons publié nos premières réflexions sur notre blog la semaine dernière, quelques instants après la révélation de l’incident. Nous sommes désormais en mesure de réaliser une analyse rétrospective plus approfondie, avec une chronologie des événements. Nous remercions les nombreuses personnes qui ont également enquêté sur cette affaire, et tout particulièrement l’utilisateur GitHub maths22, qui a procédé à la rétro-ingénierie du code malveillant.
Qu’est-ce que le paquet event-stream ?
Le paquet event-stream est une boîte à outils qui fournit des utilitaires pour créer et gérer des flux. Créé par Dominic Tarr (~dominictarr sur npmjs), il fait partie des 422 paquets dont il est propriétaire sur npmjs. Le paquet event-stream compte 84 versions, depuis la v0.5.2 en 2011, et a été régulièrement mis à jour jusqu’à la version 3.3.4, publiée deux ans auparavant.
Au cours du développement d’event-stream, le projet a reçu des contributions de 33 personnes différentes. La plupart ont été apportées à ses débuts ; depuis, seules des modifications mineures ont été examinées :

Le projet avait reçu plus de 2 000 étoiles, avait été forké 139 fois et 62 utilisateurs GitHub s’étaient inscrits aux notifications concernant les changements apportés au projet. Il était utilisé par 3 931 autres paquets (hors paquets à portée limitée).
Chronologie des événements
Voici une chronologie des principales étapes de l’histoire du projet et des moments clés de l’incident malveillant. Nous allons examiner en détail chacun de ces événements, et bien d’autres, ci-dessous.

Chaîne des événements
Examinons la chaîne des événements qui a conduit à l’utilisation du paquet malveillant flatmap-stream. Ces événements ont été reconstitués à partir d’informations publiques de GitHub, du cache Google et de npm.
31 juillet 2015 : l’utilisateur GitHub devinus commente une issue du projet event-stream pour demander si une fonctionnalité flatmap serait la bienvenue. Le mainteneur du paquet, dominictarr, répond par l’affirmative et précise qu’il accepterait une contribution d’un utilisateur :

On peut supposer que right9ctrl, l’utilisateur malveillant qui sera découvert par la suite, a pu se servir de ces informations pour planifier et mener une attaque d’ingénierie sociale élaborée contre le projet.
5 août 2018 : un utilisateur se présentant sous le nom de « Antonio Macias » a créé et publié sur npm un paquet non malveillant nommé flatmap-stream.
Antonio Macias a ensuite proposé que le projet event-stream utilise le paquet flatmap. L’utilisateur GitHub right9ctrl a contacté Dominic Tarr pour lui proposer son aide sur le projet et effectuer les modifications nécessaires à l’ajout de la fonctionnalité flatmap, en intégrant la dépendance flatmap-stream. Dominic a accepté la proposition de right9ctrl et l’a ajouté comme contributeur au projet GitHub event-stream. Il a également accordé à right9ctrl tous les droits de publication du module dans l’écosystème npm. Dominic a confirmé par la suite, dans son rapport sur l’incident, qu’il n’avait plus aucun droit de publication pour le module sur npm et ne pouvait donc pas remédier à l’incident (par exemple, en retirant de npm la version infectée 3.3.6).
Peu après, right9ctrl a soumis une série de commits anodins au dépôt GitHub d’event-stream :
4 septembre 2018 : une mise à jour de dépendance sans risque.
4 et 5 septembre 2018 : ajout d’exemples map et split 0cc, ee8, c08, 05b
5 septembre 2018 : publication de la version 3.3.5 d’event-stream avec 918.
9 septembre 2018 : ajout de la dépendance flatmap-stream dans e31
9 septembre 2018 : création d’une nouvelle version mineure 3.3.6 d’event-stream dans 599
16 septembre 2018 : suppression de flatmap-stream du code d’event-stream dans 908 et de l’arbre des dépendances dans 2bd, puis publication d’une version majeure, 4.0.0
20 septembre 2018 : right9ctrl apporte d’autres modifications cosmétiques au code pour améliorer les mots-clés du projet dans 60d, vraisemblablement afin d’améliorer son référencement sur le registre officiel npmjs.com.
5 octobre 2018 : publication d’une nouvelle version mineure, flatmap-stream@0.1.1, contenant l’attaque par injection dans son code source minifié. Les installations d’event-stream récupèrent désormais aussi la nouvelle version infectée 0.1.1 de flatmap en tant que dépendance indirecte.
Aucun autre signe d’activité de l’utilisateur right9ctrl sur le projet event-stream n’a été relevé. Son profil a été supprimé de GitHub et de npm, mais il reste accessible via le cache Google à des fins d’analyse :

29 octobre 2018 : jaydenseric ouvre une issue concernant nodemon pour signaler un avertissement de dépréciation inattendu. Ce message est conforme à la recommandation d’OpenSSL d’utiliser un algorithme plus moderne qu’EVP_BytesToKey. Il est recommandé aux développeurs de dériver eux-mêmes une clé et un IV à l’aide de crypto.scrypt() et d’utiliser crypto.createDecipheriv() pour créer l’objet Decipher.

19 novembre 2018 : NewEraCracker ouvre une issue concernant event-stream.

19 novembre 2018 : NewEraCracker ouvre une issue concernant nodemon.

20 novembre 2018 : FallingSnow suspecte une attaque par injection.
20 novembre 2018 : FallingSnow ouvre l’issue concernant event-stream.
26 novembre 2018 : un article paraît sur HackerNews et l’affaire se propage sur les réseaux sociaux.
26 novembre 2018 : le paquet flatmap-stream est supprimé de npm.
26 novembre 2018 : plusieurs utilisateurs signalent le problème à Snyk via le formulaire de signalement de vulnérabilité.
26 novembre 2018 : Snyk ajoute une fiche sur le paquet malveillant à sa base de données des vulnérabilités et avertit tous les projets surveillés.
26 novembre 2018 : Danny Grander, de Snyk, signale le problème au groupe de travail sur la sécurité de la Node.js Foundation.
27 novembre 2018 : Snyk publie un article de blog sur le problème.
La cible : Copay
En examinant plus en détail le code de flatmap-stream, on constate qu’il s’agissait d’une attaque ciblant avec précision Copay, une plateforme de portefeuille bitcoin sécurisé.
Le code malveillant de flatmap-stream a été téléchargé des millions de fois et exécuté des millions de fois encore. Les attaquants auraient pu commettre d’innombrables méfaits. Mais ils ont plutôt attendu le moment où l’application Copay serait compilée pour que leur code s’exécute. Ils ont réussi : le code a été intégré aux versions 5.0.2 à 5.1.0 de Copay.
Le code de déchiffrement recherchait la clé dans une variable d’environnement nommée npm_package_description. npm définit cette variable à partir de la description du paquet racine. Le code n’était déchiffré que si l’application cliente était le portefeuille bitcoin Copay, qui utilisait comme clé « A Secure Bitcoin Wallet » pour déchiffrer la charge utile. maths22 a trouvé cette clé en testant par force brute différentes descriptions de paquets npm.
Pour comprendre le mécanisme, l’utilisateur maths22 a testé différentes descriptions de paquets npm, utilisées comme clés pour déchiffrer la charge utile. Mais ce n’était pas tout : la deuxième charge utile s’exécutait lors de l’utilisation de commandes de compilation spécifiques, essentiellement lors de la compilation des applications iOS, Android ou de bureau.
La troisième et dernière charge utile est du code JavaScript injecté dans une autre dépendance, à savoir ./node_modules/@zxing/library/esm5/core/common/reedsolomon/ReedSolomonDecoder.js. Il était ensuite exécuté au sein même de l’application, contrairement aux deux premières charges utiles, exécutées lors de la compilation.
Le code malveillant dérobait des bitcoins ainsi que les clés privées du portefeuille lorsque son solde dépassait 100 bitcoins ou 1 000 BHC (Bitcoin Cash). Copay a conseillé ce qui suit à ses utilisateurs :
Les utilisateurs ne doivent pas tenter de transférer leurs fonds vers de nouveaux portefeuilles en important les phrases de sauvegarde de douze mots des portefeuilles concernés, car les clés privées associées sont potentiellement compromises. Ils doivent d’abord mettre à jour leurs portefeuilles concernés (versions 5.0.2 à 5.1.0), puis transférer tous les fonds vers un tout nouveau portefeuille en version 5.2.0, en utilisant la fonctionnalité Send Max pour envoyer la totalité des fonds.
Il a également été conseillé aux utilisateurs de « considérer comme compromises » leurs clés privées et de transférer « immédiatement » tous leurs avoirs vers de nouveaux portefeuilles sécurisés en version 5.2.0.
L’analyse rétrospective des événements et de l’attaque montre qu’il s’agissait d’une opération minutieusement planifiée et exécutée par des professionnels, qui a probablement nécessité des mois de préparation.
Conclusion
La série d’événements décrite dans cet article rappelle une nouvelle fois la fragilité du modèle open source lorsqu’il n’est pas soutenu à sa juste valeur. Si une petite partie seulement des utilisateurs de paquets largement utilisés comme event-stream les soutenait et contribuait à leur valeur, cette prise de contrôle malveillante aurait facilement pu être évitée. Le paquet event-stream était une dépendance dans tout l’écosystème npm, notamment dans au moins 3 931 paquets. Il affectait en particulier des paquets de premier niveau comme : @vue/cli-ui, vscode, nodemon et ps-tree.
Le paquet malveillant aurait pu passer inaperçu si un message de dépréciation n’avait pas poussé Jayden Seric à ouvrir une issue concernant le paquet nodemon. Sans cela, il est probable qu’il serait resté longtemps introuvable.
Chez Snyk, nous défendons activement la divulgation responsable et la recherche en sécurité fait partie de notre culture. Si vous découvrez une vulnérabilité que vous souhaitez divulguer de manière responsable, vous pouvez nous contacter via notre formulaire de divulgation responsable.
Lancez-vous dans les challenges Capture The Flag
Apprenez à résoudre des challenges Capture The Flag en regardant à la demande notre atelier virtuel d’initiation.
