Blog / Sicherheitsnachrichten

Axios npm-Kompromittierung: Wie die Vertrauenskette brach

Die Axios-Kompromittierung auf npm war kein Codefehler. Sechs Glieder der Vertrauenskette, die Kontrolle für jedes Glied und was npm 12 ändert.

Promotionsstudium in Informationssicherheit abgeschlossenUniversitäre Verteidigung bestanden · Abschließende Dissertationsverteidigung steht noch aus

Der Code war in Ordnung. Der Herausgeber nicht

Zwei Registry-Einträge, eine Version Abstand. Axios 1.14.0 wurde von GitHub Actions über einen Trusted Publisher veröffentlicht und trägt Provenance. Axios 1.14.1, am 31. März 2026 um 00:21 UTC erschienen, wurde von Hand veröffentlicht, ohne all das. Dieser Unterschied ist die gesamte Geschichte der Axios-npm-Kompromittierung, und er stand in den Registry-Metadaten. [2]

Die Fakten in Kürze. Axios 1.14.1 und 0.30.4, im Abstand von 39 Minuten veröffentlicht, fügten eine neue Abhängigkeit hinzu, plain-crypto-js@4.2.1, deren postinstall-Skript auf jedem Rechner, der die Installation ausführte, einen Fernzugriffstrojaner ablegte. Bis 03:15 UTC entfernte npm beide Versionen. [1][2] Google ordnet die Operation mit hoher Sicherheit UNC1069 zu, einer Gruppe mit Nordkorea-Bezug; Microsoft führt den Akteur als Sapphire Sleet. [3][4]

Ich nenne das einen Angriff auf die Vertrauenskette. Ein Software-Supply-Chain-Angriff verändert den Code, den Sie ausführen, ohne eine Zeile zu ändern, die Sie geprüft haben, indem er den Auslieferungsweg einer Abhängigkeit kompromittiert. Eine Schwachstelle im Axios-Quellcode wurde nicht ausgenutzt. Jedes Glied war eine Vertrauensentscheidung, und so lässt sich der Vorfall am nützlichsten lesen. Ich habe nichts davon selbst reproduziert; die Analyse stützt sich auf den Rückblick der Maintainer, Herstellerberichte und die Registry-Einträge, Stand 7. Oktober 2026.

axios@1.14.0   published by CI
  _npmUser           GitHub Actions
  trustedPublisher   github
  gitHead            46bee3dea75ef53a8eae49f3b7487e6341de6074
  dist.attestations  SLSA provenance v1

axios@1.14.1   2026-03-31T00:21:58Z, published by hand
  _npmUser           the lead maintainer's account
  trustedPublisher   absent
  gitHead            absent
  GitHub commit/tag  none; the release exists only on npm

Die Kette begann auf einem Laptop und endete auf einem Laptop

Das erste Glied war weder npm noch GitHub. Laut Rückblick der Maintainer verschaffte sich der Angreifer über eine gezielte Social-Engineering-Kampagne und RAT-Malware Zugriff auf den Rechner des leitenden Maintainers; das lieferte die npm-Zugangsdaten, mit denen veröffentlicht wurde. [1] Ein RAT auf einem Entwicklerrechner erzeugte das Token. Das Token erzeugte einen RAT auf jedem Entwicklerrechner und CI-Runner, der die vergiftete Version installierte. Dieselbe Malware-Klasse an beiden Enden.

Die Maintainer sind deutlich: Die Veröffentlichung direkt aus einem persönlichen Konto sei ein vermeidbares Risiko gewesen, und der OIDC-Ablauf sowie die unveränderlichen Releases, die sie nun einführen, hätten vor dem Vorfall bestehen sollen. [1] Diesen Satz schätze ich. Er benennt die eigentliche Lehre, und die betrifft nicht Axios, sondern den Ort, an dem Veröffentlichungsrechte liegen.

Ich lese den Vorfall als sechs Glieder, unten dargestellt. An jedem Glied hätte eine Kontrolle die Kette brechen können, und jedes hat einen anderen Verantwortlichen. Einige gehören dem Herausgeber. Die meisten gehören Ihnen. Ich gehe sie der Reihe nach durch.

1  maintainer laptop    social engineering + RAT         -> npm credentials
2  registry gate        direct token publish accepted    -> axios 1.14.1, 0.30.4
3  decoy dependency     plain-crypto-js 4.2.0 -> 4.2.1   -> new line in package.json
4  your resolver        fresh install, no cooldown       -> poisoned tarball fetched
5  install script       postinstall: node setup.js       -> dropper runs
6  runner or laptop     reachable secrets, open egress   -> RAT, then whatever it can reach

Warum stoppte OIDC Trusted Publishing ein gestohlenes Token nicht?

Weil es nicht die einzige Tür war. Trusted Publishing lässt die Registry eine Veröffentlichung nur von einem benannten CI-Workflow annehmen, mit einer kurzlebigen OIDC-Berechtigung statt eines gespeicherten Geheimnisses. Axios-1.x-Releases liefen bereits so, weshalb 1.14.0 einen Trusted Publisher und Provenance zeigt. [2][6] Das schädliche 1.14.1 wurde direkt aus einem Konto veröffentlicht: ohne Trusted Publisher, ohne gitHead und ohne passenden Commit oder Tag im Repository. [2]

Ein guter Veröffentlichungsweg beseitigt den schlechten nicht. Solange das Paket Tokens akzeptiert, umgeht jeder, der eines besitzt, den Workflow vollständig. Geschlossen wird das durch eine Paketeinstellung: Require two-factor authentication and disallow tokens. Die npm-Dokumentation empfiehlt sie, sobald Trusted Publisher konfiguriert sind, und diese laufen unverändert weiter. [6] StepSecurity liest die fehlenden Metadaten als gestohlenes langlebiges klassisches Token; die Maintainer sagen lediglich, der Angreifer habe die Kontozugangsdaten erlangt. [1][2] In jedem Fall berührte die Veröffentlichung den Workflow nie.

Meine Meinung: OIDC einzuschalten, ohne Tokens abzuschalten, ist eine halbe Kontrolle, und viele Teams setzen nach der ersten Hälfte den Haken. Wenn Sie Pakete veröffentlichen, prüfen Sie heute beide Hälften. Wenn Sie sie nutzen, sind die Registry-Metadaten ein lesbares Signal: Eine Version, die früher von einem Trusted Publisher stammte und plötzlich nicht mehr, verdient einen Halt. Auch npm zieht auf der anderen Seite an: Ab Januar 2027 verlieren granulare Tokens, die die Zwei-Faktor-Authentifizierung umgehen, die Möglichkeit, direkt zu veröffentlichen. [7]

Eine Abhängigkeit, die niemand importiert, ist ein Befund

Der Angreifer bereitete den Boden einen Tag vorher vor. plain-crypto-js@4.2.0 erschien am 30. März um 05:57 UTC als saubere Attrappe: eine Kopie des echten crypto-js-Codes ohne Install-Hook. Ihre einzige Aufgabe war, dem Paket eine Veröffentlichungshistorie zu geben, damit es nicht brandneu wirkte. Die schädliche 4.2.1 folgte um 23:59 UTC, 22 Minuten vor axios 1.14.1. [2]

Nun das verräterische Detail. StepSecurity durchsuchte alle 86 Dateien in axios@1.14.1 und stellte fest, dass plain-crypto-js nirgends importiert oder per require eingebunden wird. [2] Der ausgelieferte Axios-Code hatte für die Abhängigkeit keine Verwendung. Zudem fehlte dem Release das husky-Skript prepare, was zu einer manuellen Veröffentlichung am normalen Release-Werkzeug vorbei passt. [2] Einfach gesagt: Ein Patch-Release fügte eine Laufzeitabhängigkeit hinzu, die das Paket nie benutzt. Bei einer solchen Zeile hält ein menschlicher Reviewer an.

Diese Prüfung würde ich zuerst automatisieren. Vergleichen Sie bei jedem Upgrade das Manifest der alten und der neuen Version. Eine neue Abhängigkeit in einem Patch-Sprung geht an einen Menschen. Dann drei Fragen: Wie alt ist das Paket, wer pflegt es, und nutzt das übergeordnete Paket es überhaupt? Die Befehle unten beantworten alle drei in unter einer Minute. Ihr Update-Bot stellt diese Fragen nicht für Sie.

# what changed in the manifest between two versions
npm diff --diff=<pkg>@<old> --diff=<pkg>@<new> package.json

# does the package actually use the new dependency?
npm pack <pkg>@<new> && tar -xzf <pkg>-<new>.tgz && grep -R "<new-dep>" package/

# how old is the new dependency, and who maintains it?
npm view <new-dep> time maintainers --json

Warum genügte npm install, um den Rechner zu übergeben?

Weil npm bei der Installation Code ausführt. Ein Lifecycle-Skript ist ein Befehl, den ein Paket in seiner package.json deklariert und den der Paketmanager automatisch ausführt; plain-crypto-js@4.2.1 deklarierte postinstall: node setup.js. [3] Das läuft, bevor Ihre Anwendung irgendetwas importiert, weshalb auch ein Projekt, das die Bibliothek zur Laufzeit nie berührte, betroffen war. StepSecurity beobachtete den ersten Aufruf des Angreifer-Servers innerhalb von zwei Sekunden nach npm install. [2]

Die Verschleierung in setup.js ist dünner, als sie wirkt. Zeichenketten werden umgekehrt, aus Base64 dekodiert und mit dem Schlüssel OrDeR_7077 und der Konstante 333 per XOR verknüpft. [5] Die Deobfuskation von StepSecurity zeigt, dass der Schlüssel durch Number() läuft: Buchstaben werden zu NaN, XOR mit null ändert nichts, und der wirksame Schlüssel lautet 0,0,0,0,0,0,7,0,7,7. [2] Das ist ein Trick gegen String-Scanner, keine Kryptografie. Die Lehre: Statische Analyse von Installationsskripten ist ein brauchbarer Auslöser, aber eine schwache Grenze.

Danach verzweigt der Dropper nach Plattform und fordert beim Angreifer-Server eine zweite Stufe an. Der POST-Body sagt dem Server, welche er liefern soll: product0 für macOS, product1 für Windows, product2 für Linux. [2] Wohin jede landet, zeigt die Tabelle. Alle drei Varianten senden alle 60 Sekunden ein Signal, Base64-kodiertes JSON, mit dem User-Agent von Internet Explorer 8 unter Windows XP. [3] Da der RAT live nachgeladen wird, steckt im Tarball keine Schadsoftware im engeren Sinn: Das Paket ist ein Downloader. Entscheidend ist daher der ausgehende Verkehr, und ein IE8-User-Agent von einem Build-Runner ist der billigste Alarm, den Sie je schreiben werden. [3]

platform  stage two lands in                   started by
win32     %TEMP%\6202033.ps1                   VBScript; powershell.exe copied to %PROGRAMDATA%\wt.exe
darwin    /Library/Caches/com.apple.act.mond   Mach-O binary, chmod +x, zsh in the background
linux     /tmp/ld.py                           nohup python3

all       POST http://sfrclak[.]com:8000/6202033
          body: packages.npm.org/product0 (macOS), product1 (Windows), product2 (Linux)
          beacon: Base64 JSON every 60 seconds
          User-Agent: mozilla/4.0 (compatible; msie 8.0; windows nt 5.1; trident/4.0)
          win32 persistence: HKCU Run key "MicrosoftUpdate" and %PROGRAMDATA%\system.bat

Warum sah der Ordner danach sauber aus?

Weil der Dropper aufräumte. Nach der Ausführung löschte setup.js sich selbst und die schädliche package.json und benannte dann eine vorbereitete package.md in package.json um. [2] Dieses Ersatzmanifest deklariert Version 4.2.0 ohne Install-Hook. Wer danach node_modules/plain-crypto-js öffnet, sieht eine harmlose 4.2.0, obwohl 4.2.1 lief. [2] Die einen Tag zuvor platzierte Attrappe zahlt sich damit ein zweites Mal aus.

Deshalb sage ich Teams: Prüfen Sie das Lockfile, nicht node_modules. Ein Lockfile-Eintrag für plain-crypto-js, in Version 4.2.0 oder 4.2.1, bedeutet, dass Ihr Installationsgraph den Angriff enthielt, und nach Googles Empfehlung gilt der Host ab dann als kompromittiert. [3] Was Sie sichern und wie Sie die Wiederherstellung belegen, steht in meinem früheren Axios-Artikel zu Build-Nachweisen. Hier bleibe ich beim Mechanismus.

Was tat der RAT? Googles Analyse nennt die Nutzlast WAVESHAPER.V2; sie beherrscht Systemaufklärung, Verzeichnislisten, beliebige Befehlsausführung und das Einschleusen von Binärdateien. [3] Das ist Zugriff, kein Diebstahl. Was ein Operator mit Zugriff auf einen Entwicklerlaptop oder CI-Runner tut, ist, die dort liegenden Tokens, Schlüssel und .env-Dateien zu lesen; wie hoch der Einsatz ist, zeigt OpenAI: Ein GitHub-Actions-Workflow im macOS-Signierprozess führte axios 1.14.1 aus, mit Signaturzertifikat und Notarisierungsmaterial in Reichweite. OpenAI fand keine Hinweise auf Zugriff auf Nutzerdaten oder veränderte Software und tauschte das Zertifikat vorsorglich aus. [12]

Wer muss sich bei diesem Fall wirklich sorgen?

Um einen kleineren und konkreteren Teil Ihrer Landschaft, als die Schlagzeilen nahelegten. Die ausgelieferte Anwendung ist nicht das Opfer. Nichts importiert plain-crypto-js, ein aus axios 1.14.1 gebautes Frontend-Bundle hatte also keinen Grund, die Nutzlast zu enthalten; das Opfer ist die Maschine, die die Installation ausführte. [2] Auch das Zeitfenster war kurz: von 00:21 UTC bis zur Entfernung um 03:15 UTC, unter drei Stunden. [1]

Damit es Sie traf, mussten drei Dinge zutreffen. Eine frische Installation löste in diesem Fenster axios 1.14.1 oder 0.30.4 auf, also eine Installation ohne passendes Lockfile oder ein Update-Bot, der binnen Stunden mergte. Installationsskripte durften laufen, was die Standardeinstellung war. Und die Maschine hielt etwas, das sich zu erreichen lohnte. Während des Vorfalls riet Microsoft, exakte Versionen festzuschreiben und automatische Abhängigkeits-Bots anzuhalten. [4] Wenn Sie diese drei Fragen für jeden Runner und Laptop beantworten können, kennen Sie Ihre Exposition. Wenn nicht, ist das der eigentliche Befund.

Acht Prüfungen vor Ihrem nächsten npm install

Hier ist die Fassung dieses Artikels, nach der Sie handeln können. Beginnen Sie mit den eigenen Repositories, dann den CI-Images, dann den Paketen, die Sie veröffentlichen. Die Liste unten ist danach geordnet, wie schnell sie sich auszahlt.

Wenn ich eine Codebasis prüfe, gehören Abhängigkeitsmanifest, Lockfile und Release-Konfiguration zum Umfang, weil die entscheidenden Vertrauensgrenzen oft dort liegen und nicht im Code, den Sie geschrieben haben. Diesen Teil der Kette erreicht ein gewöhnlicher Penetrationstest in der Regel nicht. Wenn Sie den Blick eines Angreifers darauf wollen, wie Ihr Produkt seinen Abhängigkeiten und seinem Release-Weg vertraut, schildern Sie mir, was Sie ausliefern. Wenn Sie glauben, betroffen gewesen zu sein, sichern Sie Beweise vor dem Neuaufbau und holen Sie einen spezialisierten Incident Responder dazu: Das ist eine andere Aufgabe als meine.

1  lockfile    grep -nE "plain-crypto-js" package-lock.json     # 4.2.0 or 4.2.1 means act
2  CI install  npm ci, never npm install; no bot merges within hours of a release
3  scripts     npm 12: review allowScripts | older npm: --ignore-scripts, allow by name | pnpm 10+: allowBuilds
4  cooldown    npm config set min-release-age 3 | pnpm: minimumReleaseAge
5  trust       pnpm: trustPolicy: no-downgrade
6  publishing  your packages: trusted publisher on, "disallow tokens" on, no publish token on a laptop
7  egress      build runners: default-deny outbound; alert on node spawning curl, powershell or python3
8  secrets     the install job holds no deploy, signing or cloud credentials

Quellen

Soll das auch in Ihrem Produkt geprüft werden?

Security Code-Review
Alle Artikel

08Kontakt

Sie bringen etwas mit Nutzerdaten heraus?

Senden Sie mir eine kurze Beschreibung. Ich antworte innerhalb von drei Tagen mit Rückfragen oder einem Angebotsentwurf; den Preis vereinbaren wir vor Beginn schriftlich.