Attaque supply chain npm sur Axios : Versions malveillantes déposent un cheval de Troie d'accès à distance

Attaque supply chain npm sur Axios : Versions malveillantes déposent un cheval de Troie d'accès à distance

lschvn

Deux versions malveillantes d'axios, une bibliothèque téléchargée plus de 300 millions de fois chaque semaine, profondément embed dans l'écosystème Node.js, ont été publiées sur npm le 31 mars 2026, et détectées en quelques heures. axios@1.14.1 et axios@0.30.4 étaient livrées avec une dépendance cachée qui ne faisait rien d'autre qu'exécuter un dropper postinstall. Le dropper récupérait des payloads de deuxième étape spécifiques au système d'exploitation depuis un serveur live de commande et contrôle, puis s'effaçait et remplaçait son propre manifest pour cacher les preuves.

Si vous avez installé l'une de ces versions, supposez que votre système est compromis.

L'attaque en chiffres

  • 2 versions malveillantes publiées (1.14.1 et 0.30.4)
  • ~3 heures ces versions étaient en ligne sur npm avant suppression
  • ~300M téléchargements hebdomadaires d'axios (rayon de souffle enormous)
  • 3 cibles OS (macOS, Windows, Linux), tous les payloads pré-construits
  • 18 heures l'attaquant a préparé l'infrastructure avant de déclencher

Comment l'attaque a fonctionné

Étape 1: Détournement du compte mainteneur

L'attaquant a compromis le compte npm de jasonsaayman, le mainteneur principal d'axios. Il a changé l'email enregistré pour une adresse ProtonMail contrôlée par l'attaquant, puis publié des builds malveillants manuellement via npm CLI, contournant le pipeline CI/CD normal de GitHub Actions du projet. Ce type de compromis au niveau du compte est un rappel de pourquoi le shift de l'écosystème JavaScript vers les OIDC Trusted Publishers compte : la provenance de publication cryptographique rend plus difficile pour un attaquant de faire passer un paquet malveillant pour légitime.

Un signal forensic critique : chaque release axios 1.x légitime est publiée via le mécanisme OIDC Trusted Publisher de npm, cryptographiquement lié à un workflow GitHub Actions vérifié. Le malveillant 1.14.1 rompt ce pattern entièrement, pas de trustedPublisher, pas de gitHead, pas de commit ou tag Git correspondant.

// axios@1.14.0, LÉGITIME
"_npmUser": {
  "name": "GitHub Actions",
  "email": "npm-oidc-no-reply@github.com",
  "trustedPublisher": { "id": "github", ... }
}

// axios@1.14.1, MALVEILLANT
"_npmUser": {
  "name": "jasonsaayman",
  "email": "ifstap@proton.me"
  // pas de trustedPublisher, pas de gitHead
}

Il n'y a pas de commit ou tag dans le dépôt GitHub axios correspondant à 1.14.1. La release n'existe que sur npm.

Étape 2: Mise en scène d'une dépendance fantôme

Avant de publier les versions axios backdoorées, l'attaquant a seedé un paquet malveillant sur npm : plain-crypto-js@4.2.1, publié depuis un compte séparé jetable. Ce paquet se faisait passer pour la bibliothèque crypto-js légitime, même description, même attribution d'auteur, même URL de dépôt, mais contenait un hook postinstall dont le seul travail était d'exécuter un dropper.

Un grep à travers les 86 fichiers d'axios@1.14.1 confirme : plain-crypto-js n'est jamais importé ou require()'d nulle part dans le code source axios. Il apparaît dans package.json uniquement pour déclencher le hook postinstall. Zéro ligne de code malveillant n'existe à l'intérieur d'axios lui-même.

Étape 3: Dropper RAT multi-plateforme

Le script postinstall est un fichier JavaScript heavily obfuscated. Toutes les chaînes sensibles, URL C2, commandes shell, chemins de fichiers, sont XOR-encodées dans un tableau et décodées à l'exécution en utilisant une clé dérivée de la chaîne "OrDeR_7077". Le dropper se divise en trois chemins d'attaque spécifiques au OS :

  • macOS : Télécharge un binaire RAT vers /Library/Caches/com.apple.act.mond (conçu pour imiter un cache système Apple), le rend exécutable, et le lance silencieusement via osascript
  • Windows : Copie PowerShell vers %PROGRAMDATA%\wt.exe ( déguisé en Windows Terminal), dépose un VBScript pour récupérer et exécuter un RAT de deuxième étape
  • Linux : Cible les environnements CI/CD, récupère un RAT basé sur Python

Étape 4: Auto-destruction

Après avoir déployé le payload de deuxième étape, le dropper se supprime et échange un manifest de paquet propre :

// Le propre code de nettoyage de l'attaquant intégré dans le dropper
// Remplace le package.json malveillant par un décoy propre
rename("package.md", "package.json")  // stub propre écrase les preuves
unlink("setup.js")                    // le dropper s'efface

Un développeur qui inspecte son node_modules après coup ne voit aucune indication que quelque chose s'est mal passé.

Indicateurs de compromission (IOCs)

Paquets malveillants :

  • plain-crypto-js@4.2.1, dépendance malveillante, dropper RAT postinstall
  • axios@1.14.1, release 1.x backdoorée
  • axios@0.30.4, release 0.x legacy backdoorée

Infrastructure C2 :

  • sfrclak.com:8000, serveur de commande et contrôle (livraison étape 2)
  • Segments de chemin : /6202033, packages.npm.org/product0, packages.npm.org/product1, packages.npm.org/product2

Persistance macOS :

  • /Library/Caches/com.apple.act.mond, binaire RAT déposé sur macOS

Persistance Windows :

  • %PROGRAMDATA%\wt.exe, RAT déguisé en Windows Terminal

Ce que vous devez faire maintenant

  1. Vérifiez votre node_modules pour les versions axios 1.14.1 et 0.30.4. Si présentes, supposez la compromission.
  2. Retrogradez immédiatement vers axios@1.14.0 ou axios@0.30.3, les dernières versions propres connues.
  3. Auditez vos systèmes pour les IOCs: vérifiez les connexions sortantes inattendues vers sfrclak.com:8000, les tâches cron/planifiées inhabituelles, ou les binaires inconnus dans /Library/Caches (macOS) ou %PROGRAMDATA% (Windows).
  4. Rotatez tous les secrets et identifiants accédés depuis les machines de développement qui ont exécuté npm install pendant la fenêtre où les versions malveillantes étaient en ligne (~31 mars, 00:21–03:15 UTC).
  5. Vérifiez votre workflow de publication npm: assurez-vous que votre projet utilise les OIDC Trusted Publishers pour que les publications manuelles basées sur tokens cassent la chaîne de signature.

Pourquoi cette attaque n'était pas opportuniste

La sophistication opérationnelle ici est notable :

  • L'attaquant a seedé un paquet décoy propre (plain-crypto-js@4.2.0) 18 heures avant la release malveillante, établissant un historique de publication npm pour éviter les alertes de "paquet zero-history"
  • Les deux branches de release 1.x et 0.x ont été touchées 39 minutes d'intervalle pour maximiser la couverture
  • Des payloads séparés ont été pré-construits pour trois systèmes d'exploitation
  • L'URL C2 utilise des segments de chemin liés à la structure du chemin du registre npm, packages.npm.org/product0/1/2, rendant l'exfiltration ressemble à du trafic npm ordinaire
  • Le dropper utilise le binaire osascript légitime d'Apple sur macOS, pas de binaire malveillant écrit sur le disque avant la validation, rendant la détection EDR plus difficile

C'est du savoir-faire d'attaque supply chain à un niveau précédemment réservé aux opérations sponsorisées par des États, maintenant déployé contre un paquet npm top-10.


Sources : StepSecurity, métadonnées du registre npm, dépôt GitHub axios. L'avis de sécurité axios est disponible dans la base de données des avis de sécurité npm.

Questions fréquentes

Articles connexes

Plus de couverture avec des sujets et tags en commun.

Fastify v5.9.0 ajoute `request.mediaType` et `onMaxParamLength`, durcit la confiance dans les en-têtes `forwarded`, découpe les grandes réponses HTTP/2 et migre les tests de types vers TSTyche
security

Fastify v5.9.0 ajoute `request.mediaType` et `onMaxParamLength`, durcit la confiance dans les en-têtes `forwarded`, découpe les grandes réponses HTTP/2 et migre les tests de types vers TSTyche

Fastify v5.9.0, publié le 2026-06-28 (github.com/fastify/fastify), est la première version mineure de la ligne v5 en 2026 et un cycle substantiel de 65 PR. Les fonctionnalités principales sont `request.mediaType` (un accesseur typé pour le type de média négocié, [#6653](https://github.com/fastify/fastify/pull/6653) par climba03003), l'option de route `onMaxParamLength` ([#6716](https://github.com/fastify/fastify/pull/6716) par climba03003), et un correctif de sécurité qui ne fait plus confiance à `X-Forwarded-Host` et `X-Forwarded-Proto` quand le socket entrant est absent ([#6684](https://github.com/fastify/fastify/pull/6684) par mcollina). Le cycle livre un correctif de découpage en morceaux du buffer HTTP/2 pour les grandes réponses ([#6746](https://github.com/fastify/fastify/pull/6746) par mcollina), trois gains de performance liés au schéma (parsing différé du ContentType dans `getSchemaSerializer` #6692, mise en cache des objets `ContentType` analysés dans `ContentTypeParser` #6694, garde `typeof` avant `toString.call` dans `send` / `onSendEnd` #6693 par aquie00t), Node.js 26 ajouté à la matrice de tests ([#6728](https://github.com/fastify/fastify/pull/6728) par Fdawgs) et Node.js 20 retiré de la matrice yarn CI ([#6662](https://github.com/fastify/fastify/pull/6662) par Tony133), la migration de la suite de tests de types d'assertions `expect-type` artisanales vers [TSTyche](https://github.com/mrazauskas/tstyche) ([#6532](https://github.com/fastify/fastify/pull/6532) par mrazauskas, avec les suites #6726 et #6727), et un bump de fastify-plugin v6.0.0 purement TypeScript. Autres correctifs notables : déduplication de `res.end` du trailer (#6676), garde de complétion dupliquée du trailer (#6714), `error.code` sur les erreurs de routage (#6678), `hasRequestDecorator` / `hasReplyDecorator` détectant les propriétés natives assignées par constructeur (#6753), `getValidationFunction()` autorisé à retourner `undefined` (#6665), et un nettoyage du `_meta` du socket qui ferme une fuite keep-alive (#6799).
Deno 2.9 livre un démarrage à froid 1,98x plus rapide, 2,2 à 3,1x moins de RSS en charge, le minimum-release-age npm activé par défaut, la politique de confiance no-downgrade et les tests par snapshot natifs
runtimes

Deno 2.9 livre un démarrage à froid 1,98x plus rapide, 2,2 à 3,1x moins de RSS en charge, le minimum-release-age npm activé par défaut, la politique de confiance no-downgrade et les tests par snapshot natifs

Deno 2.9 (Bartek Iwańczuk, publié le 2026-06-25 sur deno.com/blog/v2.9) est la plus grande release Deno du cycle. Le démarrage à froid passe de 34,2 ms à 17,3 ms (1,98x), le pic RSS sur la charge realworld de Deno.serve baisse d'un facteur 2,2 (142 Mo → 64 Mo) et de 3,1x sur les corps de 1 Mio (197 Mo → 63 Mo), et le débit Deno.serve grimpe de 1,27x en realworld (56,8k → 72,4k req/s), 1,11x en plaintext, et 1,18x sur les corps de 1 Mio. Durcissement supply chain : le npm minimum-release-age est activé par défaut avec une fenêtre de 24h (PR #35458), et une nouvelle politique de confiance opt-in no-downgrade (PR #34927) refuse de résoudre toute version dont la preuve de confiance (publication échelonnée, trusted publishing, attestation de provenance) est plus faible que la meilleure preuve sur toute version précédemment publiée du même paquet. Parité du runner de tests : t.assertSnapshot() natif (#35139), Deno.test.each (#34938), --shard pour fan-out CI (#35057), retry et repeats (#35053), sélection change-aware --changed et --related (#35199), et seuils de coverage (#35056). Interop lockfile : deno install seed deno.lock depuis package-lock.json, pnpm-lock.yaml, yarn.lock ou bun.lock (#34296, #35330, #35346, #35350, #35394), pnpm-workspace.yaml auto-migre vers deno.json / package.json (#34993), et les marqueurs de conflit de fusion git dans deno.lock se résolvent automatiquement (#34726). Plus : deno desktop sort de l'expérimental (PR #33441 du 16 juin), sous-commandes deno link / deno unlink / deno list / deno watch, --unsafe-proto stable (#34738), Web Locks API (#31166), Happy Eyeballs v2 (RFC 8305) (#31726), navigator.userAgentData (#34743), la proposition WebCrypto Modern Algorithms (ML-KEM, ML-DSA, SLH-DSA, ChaCha20-Poly1305, famille SHA-3, KMAC, Argon2) (#34447, #34448, #34914, #35223), compat Node 26.3.0 (#34746, #34747), Node-API v10 (#35270), et imports de modules CSS sous --unstable-raw-imports (#35093). Plus de 165 PRs mergent dans ce cycle.
Node.js 26.4.0 'Current' livre le sous-système node:vfs (Matteo Collina), les package maps pour les hooks de loader ESM (Maël Nison), la compression de certificat TLS, TCP_KEEPINTVL/TCP_KEEPCNT, et argon2 stable
security

Node.js 26.4.0 'Current' livre le sous-système node:vfs (Matteo Collina), les package maps pour les hooks de loader ESM (Maël Nison), la compression de certificat TLS, TCP_KEEPINTVL/TCP_KEEPCNT, et argon2 stable

Node.js 26.4.0 (Current), publié le 2026-06-24 par @aduh95, apporte huit changements SEMVER-MINOR : un sous-système node:vfs minimal qui permet de monter des systèmes de fichiers virtuels fournis par l'application (PR #63115, Matteo Collina) ainsi qu'un suivi qui redirige node:fs/promises vers les instances VFS montées (PR #63537), les package maps pour les hooks de loader ESM qui routent les spécificateurs bruts à travers les hooks de loader (PR #62239, Maël Nison), certificateCompression TLS qui câble la compression zlib et zstd de la RFC 8879 à travers la configuration de build OpenSSL (PR #62217, Tim Perry), le support de TCP_KEEPINTVL et TCP_KEEPCNT dans net.Socket.setKeepAlive (PR #63825, Guy Bedford), des buffers fournis par l'appelant dans fs.readFile / fs.readFileSync (PR #63634, Matteo Collina), closeIdleConnections qui ferme désormais aussi les sockets pre-request (PR #63470, semimikoh), net.BlockList promu au statut Release Candidate (PR #63050), et crypto argon2 + KEM encap/decap marqués stable (PR #63924, Filip Skokan). La release ajoute aussi WebCrypto cSHAKE (PR #63988), listEndpoints QUIC (PR #63536) et des handles X509Certificate (PR #63191), dgram connectSync / bindSync (PRs #63838 + #63932, Guy Bedford), net.BoundSocket pour un binding TCP précoce (PR #63951), un chemin expérimental d'appel FFI rapide pour AArch64 et x86_64 (PRs #63068 + #63941, Paolo Insogna), npm 11.17.0 (PR #63857), sqlite 3.53.2 et libffi 3.6.0.

Commentaires

Connexion Connectez-vous pour participer à la conversation.

Pas encore de commentaires. Soyez le premier à partager vos pensées.