Elastic Security Labs a publié les premières règles de triage et de détection pour la compromission de la chaîne d'approvisionnement d'Axios. Il s'agit d'une analyse détaillée de la RAT et des charges utiles.
Introduction
Elastic Security Labs a identifié une compromission de la chaîne d'approvisionnement du paquet axios npm, l'un des paquets les plus dépendants de l'écosystème JavaScript avec environ 100 millions de téléchargements hebdomadaires. L'attaquant a compromis le compte d'un responsable et publié des versions antidatées qui délivrent un cheval de Troie d'accès à distance multiplateforme aux systèmes macOS, Windows et Linux par le biais d'un crochet post-installation malveillant.
Principaux points abordés dans cet article
- Un compte npm maintainer compromis (jasonsaayman) a été utilisé pour publier deux versions malveillantes du client HTTP Axios largement utilisé - 1.14.1 (tagged latest) et 0.30.4 (tagged legacy) - ce qui signifie qu'un npm install axios par défaut s'est soldé par un paquetage rétroactif.
- Le JavaScript malveillant déploie des implants de stade 2 spécifiques à chaque plateforme pour macOS, Windows et Linux
- Les trois charges utiles de niveau 2 sont des implémentations du même RAT - protocole C2, jeu de commandes, cadence des balises et agent utilisateur usurpé identiques, écrits en PowerShell (Windows), C++ (macOS) et Python (Linux).
- Le dropper effectue un nettoyage anti-forensic en se supprimant lui-même et en échangeant son package.json avec une copie propre, effaçant ainsi les preuves du déclenchement post-installation de
node_modules
Préambule
Le mars 30, 2026, Elastic Security Labs a détecté une compromission de la chaîne d'approvisionnement ciblant le paquet axios npm grâce à une surveillance automatisée de la chaîne d'approvisionnement. L'attaquant a pris le contrôle du compte npm appartenant à jasonsaayman, l'un des principaux mainteneurs du projet, et a publié deux versions rétroactives en l'espace de 39 minutes.
Le paquet axios est l'une des bibliothèques client HTTP les plus utilisées dans l'écosystème JavaScript. Au moment de la découverte, les étiquettes des versions les plus récentes et les plus anciennes indiquaient des versions compromises, ce qui garantissait que la majorité des nouvelles installations utilisaient une version rétroactive.
Les versions malveillantes ont introduit une seule nouvelle dépendance : plain-crypto-js, un paquetage spécifique dont le crochet post-installation télécharge et exécute silencieusement des implants RAT de stade 2 spécifiques à la plate-forme à partir de sfrclak[.]com:8000.
Ce qui rend cette campagne remarquable au-delà de son rayon d'action, c'est l'outillage de niveau 2. L'attaquant a déployé trois implémentations parallèles du même RAT - une pour Windows, une pour macOS et une pour Linux - qui partagent toutes un protocole C2, une structure de commande et un comportement de balise identiques. Il ne s'agit pas de trois outils différents, mais d'un seul cadre d'implantation multiplateforme avec des implémentations natives de la plateforme.
Elastic Security Labs a déposé un avis de sécurité GitHub sur le dépôt axios le 31 mars, 2026 à 01:50 AM UTC pour coordonner la divulgation et s'assurer que les mainteneurs et le registre npm puissent agir sur les versions compromises.
La communauté ayant signalé la compromission sur les médias sociaux, Elastic Security Labs a partagé publiquement les premières conclusions afin d'aider les défenseurs à réagir en temps réel.
Ce billet couvre l'ensemble de la chaîne d'attaque : de la compromission de la chaîne d'approvisionnement au niveau npm, en passant par le dropper obscurci, jusqu'à l'architecture du RAT multiplateforme et les différences significatives entre ses trois variantes.
Aperçu de la campagne
Le compromis est évident dans les métadonnées du registre npm. L'adresse électronique du responsable est passée de jasonsaayman@gmail[.]com - présente dans toutes les versions légitimes antérieures - à ifstap@proton[.]me dans les versions malveillantes. La méthode de publication a également changé :
| Version | Publié par | Method | Provenance |
|---|---|---|---|
axios@1.14.0 (légitime) | jasonsaayman@gmail[.]com | Actions GitHub OIDC | Attestations de provenance SLSA |
axios@1.14.1 (compromis) | ifstap@proton[.]me | Publication directe CLI | Aucune |
axios@0.30.4 (compromis) | ifstap@proton[.]me | Publication directe CLI | Aucune |
Le passage d'un flux d'éditeurs OIDC de confiance avec une provenance SLSA à une publication CLI directe avec un e-mail modifié est un indicateur clair d'un accès non autorisé.
Chronologie
- 2026-02-18 17:19 UTC -
axios@0.30.3publié légitimement parjasonsaayman@gmail[.]com - 2026-03-27 19:01 UTC -
axios@1.14.0publié légitimement via GitHub Actions OIDC - 2026-03-30 05:57 UTC -
plain-crypto-js@4.2.0publié parnrwise(nrwise@proton.me) - clean decoy to build registry history - 2026-03-30 23:59 UTC -
plain-crypto-js@4.2.1publié parnrwise- version malveillante avecpostinstallbackdoor - 2026-03-31 00:21 UTC -
axios@1.14.1publié par un compte compromis - taguélatest - 2026-03-31 01:00 UTC -
axios@0.30.4publié par un compte compromis - taguélegacy
Paquets concernés
axios@1.14.1- Malveillante, étiquetéelatestau moment de la découverteaxios@0.30.4- Malveillante, étiquetéelegacyau moment de la découverteplain-crypto-js@4.2.0- Leurre propre, publié pour construire l'historique du registreplain-crypto-js@4.2.1- Véhicule malveillant de livraison de charge utile (postinstallbackdoor)
Versions sûres : axios@1.14.0 (dernière version légitime 1.x avec provenance SLSA) et axios@0.30.3 (dernière version légitime 0.30.x ).
L'attaquant a marqué les canaux les plus récents et les plus anciens, maximisant ainsi le rayon d'action des projets utilisant l'API axios actuelle ou ancienne.
Analyse du code
Étape 1 : Le compte-gouttes plain-crypto-js
L'ensemble de la chaîne de livraison repose sur le crochet de cycle de vie post-installation de npm. L'installation de l'une ou l'autre des versions compromises d'axios tire plain-crypto-js@^4.2.1 comme dépendance, qui déclare :
"scripts": {
"postinstall": "node setup.js"
}
Cela fait en sorte que setup.js s'exécute automatiquement pendant l'installation de npm - aucune interaction de la part de l'utilisateur n'est requise.
Le fichier setup.js utilise un système d'encodage à deux niveaux pour dissimuler son comportement :
- Couche 1 : Inversion de la chaîne de caractères suivie d'un décodage Base64
- Couche 2 : Chiffrement XOR utilisant la clé OrDeR_7077 avec un indice dépendant de la position (7 * i² % 10)
Toutes les chaînes critiques, les noms de modules, les URL, les commandes shell sont stockés dans un tableau codé stq[] et décodés au moment de l'exécution. Le contenu décodé révèle l'infrastructure opérationnelle :
Livraison spécifique à la plate-forme
Après avoir décodé sa table de chaînes, le dropper vérifie os.platform() et passe à l'une des trois routines de livraison. Chacun envoie un POST HTTP à http://sfrclak[.]com:8000/6202033 avec un corps spécifique à la plateforme - packages.npm.org/product0 (macOS), packages.npm.org/product1 (Windows), packages.npm.org/product2 (Linux) - permettant au C2 de servir la bonne charge utile à partir d'un seul point d'extrémité. Le site packages.npm.org/ est une tentative délibérée de faire apparaître le trafic sortant comme une communication bénigne du registre npm dans les journaux du réseau :
| Platform | Méthode de livraison | Lieu de l'étape 2 | Déguisement |
|---|---|---|---|
| macOS | AppleScript via osascript télécharge un binaire avec curl | /Library/Caches/com.apple.act.mond | Démon du système Apple |
| Windows | Téléchargements VBScript .ps1 via curl, s'exécute via PowerShell renommé (%PROGRAMDATA%\wt.exe) | %TEMP%\6202033.ps1 (transitoire) | Terminal Windows |
| Linux | Téléchargement direct de curl et exécution de python3 | /tmp/ld.py | Aucune |
Anti-forensics
Le compte-gouttes effectue deux opérations de nettoyage :
- Auto-effacement : setup.js se supprime lui-même via fs.unlink(__filename)
- Échange de manifeste de paquet : Un fichier propre nommé package.md (contenant une configuration bénigne de la version 4.2.0 sans crochet de post-installation) est renommé en package.json, l'écrasement de la version malveillante
L'inspection post-incident de node_modules/plain-crypto-js/package.json ne révèle aucune trace du déclencheur post-installation. Le fichier malveillant setup.js a disparu. Seuls le fichier de verrouillage et les journaux d'audit npm conservent des preuves.
Étape 2 : RAT multiplateforme
Les trois charges utiles de l'étape 2 : PowerShell pour Windows, C++ compilé pour macOS, Python pour Linux ne sont pas trois outils différents. Il s'agit de trois implémentations de la même spécification RAT, partageant un protocole C2, un ensemble de commandes, un format de message et un comportement opérationnel identiques. Cette cohérence indique clairement qu'il s'agit d'un développeur unique ou d'une équipe étroitement coordonnée travaillant à partir d'un document de conception commun.
Architecture partagée
Les propriétés suivantes sont identiques pour les trois variantes :
- Transport C2 : HTTP POST
- Encodage du corps : JSON encodé en base64
- User-Agent :
mozilla/4.0 (compatible; msie 8.0; windows nt 5.1; trident/4.0) - Intervalle entre les balises : 60 secondes
- Session UID : chaîne alphanumérique aléatoire de 16 caractères, générée par exécution.
- Types de messages sortants :
FirstInfo,BaseInfo,CmdResult - Types de commandes entrantes :
kill,peinject,runscript,rundir - Types de commandes de réponse :
rsp_kill,rsp_peinject,rsp_runscript,rsp_rundir
La chaîne usurpée du user-agent d'IE8/Windows XP est particulièrement remarquable, elle est anachronique sur les trois plateformes, et sa présence sur un hôte macOS ou Linux est un indicateur de détection fort.
Initialisation et reconnaissance
Au démarrage, chaque variante :
- Génère un UID de session - 16 caractères alphanumériques aléatoires, inclus dans chaque message C2 ultérieur.
- Détecte le système d'exploitation et l'architecture - signale les identifiants spécifiques à la plate-forme (par exemple, windows_x64, macOS, linux_x64)
- Enumère les répertoires initiaux d'intérêt (profil de l'utilisateur, documents, bureau, répertoires de configuration).
- Envoie une balise FirstInfo contenant l'UID, l'identifiant du système d'exploitation et l'instantané du répertoire.
Après l'initialisation, l'implant entre dans la boucle principale. Le premier battement de cœur de BaseInfo comprend un profil complet du système. Les mêmes catégories de données sont collectées sur toutes les plateformes, bien que les API sous-jacentes diffèrent :
| Données collectées | Source Windows | macOS Source | Linux Source |
|---|---|---|---|
| Nom d'hôte | %COMPUTERNAME% env var | gethostname() | /proc/sys/kernel/hostname |
| Nom d'utilisateur | %USERNAME% env var | getuid() + getpwuid() | os.getlogin() |
| Version du système d'exploitation | WMI / registre | sysctlbyname("kern.osproductversion") | plateforme.système() + platform.release() |
| Fuseau horaire | Fuseau horaire du système | localtime_r() | datetime.timezone |
| Temps de démarrage | Durée de fonctionnement du système | sysctl("kern.boottime") | /proc/uptime |
| Date d'installation | Registre / WMI | stat("/") ou sysctl | ctime de /var/log/installer ou /var/log/dpkg.log |
| Modèle de matériel | WMI | sysctlbyname("hw.model") | /sys/class/dmi/id/nom_du_produit |
| Type de CPU | WMI | sysctlbyname() | plateforme.machine() |
| Process list | PID complet, session, nom, chemin | popen("ps") (jusqu'à 1000) | Énumération complète de /proc (PID, PPID, utilisateur, cmdline) |
Les battements de cœur suivants sont légers et ne contiennent qu'un horodatage confirmant que l'implant est en vie.
Envoi des commandes
La réponse C2 est analysée sous forme de JSON, et le champ type détermine l'action. Les trois variantes mettent en œuvre les mêmes quatre commandes :
kill - Auto-termination. Envoie un accusé de réception rsp_kill et quitte. Le mécanisme de persistance de la variante Windows (clé de registre + fichier batch) survit à la commande kill à moins d'être explicitement nettoyé ; les variantes macOS et Linux n'ont pas de persistance propre.
runscript - Exécution d'un script ou d'une commande. La commande d'interaction principale de l'opérateur. Accepte un champ Script (code à exécuter) et un champ Param (arguments). Lorsque Script est vide, Param est exécuté directement comme une commande. Le mécanisme d'exécution est indépendant de la plate-forme :
| Platform | Mécanisme d'exécution |
|---|---|
| Windows | PowerShell avec -NoProfile -ep Bypass |
| macOS | AppleScript via /usr/bin/osascript |
| Linux | Shell via subprocess.run(shell=True) ou Python via python3 -c |
peinject - Livraison de charges utiles binaires. Malgré la dénomination centrée sur Windows ("PE inject"), les trois plates-formes l'utilisent comme moyen de déposer et d'exécuter des charges utiles binaires :
| Platform | Implementation |
|---|---|
| Windows | Chargement réfléchi d'un assemblage .NET via [System.Reflection.Assembly]::Load() |
| macOS | Décode en base64 et dépose un binaire, s'exécute avec les paramètres fournis par l'opérateur. |
| Linux | Base64-décode un binaire vers /tmp/.<random Chaîne de 6 caractères> (fichier caché), lancée via subprocess.Popen(). |
L'implémentation Windows dispose d'une exécution en mémoire sans dépôt de fichier mais sans désactiver AMSI qui sera certainement signalé lors du chargement de l'assemblage. Les variantes macOS et Linux adoptent l'approche la plus simple, qui consiste à écrire un binaire sur le disque et à l'exécuter directement.
rundir - Énumération des répertoires. Accepte les chemins d'accès et renvoie une liste détaillée des fichiers (nom, taille, type, date de création/modification, nombre d'enfants pour les répertoires). Permet à l'opérateur de parcourir le système de fichiers de manière interactive.
Résumé des capacités
| Capacité | Windows (PowerShell) | macOS (C++) | Linux (Python) |
|---|---|---|---|
| Persistance | Clé d'exécution du registre + .bat caché | Aucune | Aucune |
| Exécution du script | PowerShell | AppleScript via osascript | Shell ou Python en ligne |
| Injection binaire | Injection de charge .NET réfléchie dans cmd.exe | Binary drop + execute | Binary drop to /tmp/ + executez |
| Anti-forensics | Fenêtres cachées, nettoyage des fichiers temporaires | Temp caché .scpt | Fichiers cachés /tmp/.XXXXXX |
Attribution
Le binaire macOS Mach-O fourni par le crochet de post-installation plain-crypto-js présente des similitudes importantes avec WAVESHAPER, une porte dérobée C++ repérée par Mandiant et attribuée à UNC1069, un groupe de menaces lié à la République populaire démocratique de Corée (RPDC).
Conclusion
Cette campagne démontre l'attrait continu de l'écosystème npm en tant que vecteur d'attaque de la chaîne d'approvisionnement. En compromettant le compte d'un seul responsable de l'un des paquets les plus dépendants de l'écosystème JavaScript, l'auteur de l'attaque a obtenu un mécanisme de diffusion susceptible d'atteindre des millions d'environnements.
L'indicateur de détection le plus fiable de la boîte à outils est aussi son choix de conception le plus curieux : la chaîne du user-agent d'IE8/Windows XP codée en dur de manière identique sur les trois variantes de plate-forme. Bien qu'il fournisse une empreinte protocolaire cohérente pour le routage côté serveur C2, il est trivialement détectable sur n'importe quel réseau moderne - et constitue une anomalie immédiate sur les hôtes macOS et Linux.
Elastic Security Labs continuera à surveiller ce groupe d'activités et mettra à jour cet article en cas de nouvelles découvertes.
MITRE ATT&CK
Elastic utilise le cadre MITRE ATT& CK pour documenter les tactiques, techniques et procédures communes que les menaces persistantes avancées utilisent contre les réseaux d'entreprise.
Tactiques
Les tactiques représentent le pourquoi d'une technique ou d'une sous-technique. Il s'agit de l'objectif tactique de l'adversaire : la raison pour laquelle il effectue une action.
Techniques
Les techniques représentent la manière dont un adversaire atteint un objectif tactique en effectuant une action.
- Compromission de la chaîne d'approvisionnement : Compromission des dépendances logicielles
- Interprète de commandes et de scripts : JavaScript
- Interprète de commandes et de scripts : PowerShell
- Interprète de commandes et de scripts : AppleScript
- Command and Scripting Interpreter: Unix Shell
- Command and Scripting Interpreter: Python
- Exécution du démarrage automatique au démarrage ou à l'ouverture de session : Clés d'exécution du registre
- Masquage des fichiers ou des informations
- Mascarade
- Fichiers et répertoires cachés
- Injection de processus
- Indicator Removal: File Deletion
- Recherche d'informations sur le système
- Découverte du processus
- Détection de fichiers et de répertoires
- Application Layer Protocol: Web Protocols
- Port non standard
- Encodage des données : Codage standard
- Transfert d'outils d'infiltration
Observations
Les observables suivants ont été examinés dans le cadre de cette recherche.
| Observable | Type | Nom | Référence |
|---|---|---|---|
617b67a8e1210e4fc87c92d1d1da45a2f311c08d26e89b12307cf583c900d101 | SHA-256 | 6202033.ps1 | Charge utile Windows |
92ff08773995ebc8d55ec4b8e1a225d0d1e51efa4ef88b8849d0071230c9645a | SHA-256 | com.apple.act.mond | Charge utile MacOS |
fcb81618bb15edfdedfb638b4c08a2af9cac9ecfa551af135a8402bf980375cf | SHA-256 | ld.py | Charge utile Linux |
sfrclak[.]com | Domain | C2 | |
142.11.206[.]73 | ipv4-addr | C2 |
Références
Les éléments suivants ont été référencés tout au long de la recherche ci-dessus :