🎯 Contexte

Publié le 20 mars 2026 par Aikido Security (Charlie Eriksen), cet article documente la détection le 20 mars 2026 à 20h45 UTC d’une campagne de compromission massive de packages NPM, baptisée CanisterWorm, attribuée au groupe TeamPCP. Cette attaque fait suite à une compromission de l’outil Trivy moins de 24 heures auparavant, documentée par Wiz.

📦 Packages compromis

  • 28 packages dans le scope @EmilGroup
  • 16 packages dans le scope @opengov
  • @teale.io/eslint-config
  • @airtm/uuid-base32
  • @pypestream/floating-ui-dom

🏗️ Architecture en trois étapes

  1. Stage 1 – Loader Node.js (postinstall) : Un hook postinstall dans index.js décode un payload base64 (script Python), crée un service systemd utilisateur pgmon.service avec Restart=always, et le démarre immédiatement. Aucun accès root requis.
  2. Stage 2 – Backdoor Python persistante : Le script service.py attend 5 minutes (évasion sandbox), puis interroge toutes les ~50 minutes un canister ICP (tdtqy-oyaaa-aaaae-af2dq-cai.raw.icp0.io) pour obtenir une URL de payload. Il télécharge le binaire vers /tmp/pglog, l’exécute en processus détaché, et sauvegarde l’URL dans /tmp/.pg_state. Un kill switch est intégré : si l’URL contient youtube.com, le payload est ignoré.
  3. Stage 3 – Ver de propagation (deploy.js) : Outil initialement manuel utilisant des tokens NPM volés pour énumérer tous les packages publiables d’un compte, incrémenter la version patch, préserver le README original, et republier avec --tag latest.

🐛 Évolution vers l’auto-propagation

Environ une heure après la vague initiale, une mise à jour de @teale.io/eslint-config (versions 1.8.11 et 1.8.12) a introduit la fonction findNpmTokens() qui :

  • Scrape ~/.npmrc, .npmrc local et /etc/npmrc
  • Inspecte les variables d’environnement (NPM_TOKEN, NPM_TOKENS, *NPM*TOKEN*)
  • Interroge npm config get en subprocess
  • Lance automatiquement deploy.js en processus détaché avec les tokens récoltés

Cette évolution transforme chaque développeur ou pipeline CI/CD installant le package en vecteur de propagation involontaire.

🔑 Caractéristiques notables

  • C2 décentralisé via ICP : premier usage documenté d’un canister Internet Computer comme dead-drop C2, résistant aux takedowns
  • Rotation de payload à distance : le contrôleur du canister peut changer l’URL à tout moment
  • Masquage PostgreSQL : artefacts nommés pgmon, pglog, .pg_state
  • Quatre vagues documentées avec hashes SHA256 distincts pour index.js et deploy.js
  • Message laissé dans le code source par le threat actor s’adressant directement à l’auteur de l’article

📊 Type d’article

Analyse technique détaillée avec extraction complète des IOCs, codes sources malveillants annotés et chronologie des vagues d’attaque, destinée aux équipes CTI et sécurité supply chain.

🧠 TTPs et IOCs détectés

Acteurs de menace

  • TeamPCP (unknown)

TTP

  • T1195.002 — Supply Chain Compromise: Compromise Software Supply Chain (Initial Access)
  • T1059.006 — Command and Scripting Interpreter: Python (Execution)
  • T1059.004 — Command and Scripting Interpreter: Unix Shell (Execution)
  • T1543.001 — Create or Modify System Process: Launch Agent (Persistence)
  • T1053.003 — Scheduled Task/Job: Cron (Persistence)
  • T1027 — Obfuscated Files or Information (Defense Evasion)
  • T1036 — Masquerading (Defense Evasion)
  • T1497.003 — Virtualization/Sandbox Evasion: Time Based Evasion (Defense Evasion)
  • T1552.001 — Unsecured Credentials: Credentials In Files (Credential Access)
  • T1552.007 — Unsecured Credentials: Container API (Credential Access)
  • T1071.001 — Application Layer Protocol: Web Protocols (Command and Control)
  • T1102 — Web Service (Command and Control)
  • T1105 — Ingress Tool Transfer (Command and Control)
  • T1543 — Create or Modify System Process (Persistence)
  • T1078 — Valid Accounts (Initial Access)

IOC

  • Domaines : tdtqy-oyaaa-aaaae-af2dq-cai.raw.icp0.io
  • URLs : https://tdtqy-oyaaa-aaaae-af2dq-cai.raw.icp0.io/
  • SHA256 : e9b1e069efc778c1e77fb3f5fcc3bd3580bbc810604cbf4347897ddb4b8c163b
  • SHA256 : 61ff00a81b19624adaad425b9129ba2f312f4ab76fb5ddc2c628a5037d31a4ba
  • SHA256 : 0c0d206d5e68c0cf64d57ffa8bc5b1dad54f2dda52f24e96e02e237498cb9c3a
  • SHA256 : c37c0ae9641d2e5329fcdee847a756bf1140fdb7f0b7c78a40fdc39055e7d926
  • SHA256 : f398f06eefcd3558c38820a397e3193856e4e6e7c67f81ecc8e533275284b152
  • SHA256 : 7df6cef7ab9aae2ea08f2f872f6456b5d51d896ddda907a238cd6668ccdc4bb7
  • SHA256 : 5e2ba7c4c53fa6e0cef58011acdd50682cf83fb7b989712d2fcf1b5173bad956
  • Fichiers : service.py
  • Fichiers : pgmon.service
  • Fichiers : pglog
  • Fichiers : deploy.js
  • Fichiers : index.js
  • Chemins : ~/.local/share/pgmon/service.py
  • Chemins : ~/.config/systemd/user/pgmon.service
  • Chemins : /tmp/pglog
  • Chemins : /tmp/.pg_state

Malware / Outils

  • CanisterWorm (other)
  • deploy.js (tool)
  • service.py (backdoor)
  • pgmon (backdoor)

🔗 Source originale : https://www.aikido.dev/blog/teampcp-deploys-worm-npm-trivy-compromise