Dependency-Confusion-Angriffe auf npm erkennen und verhindern, um die Supply-Chain-Sicherheit zu gewährleisten
13. September 2021
0 Min. LesezeitAm 9. Februar 2021 veröffentlichte Alex Birsan seine treffend benannte Sicherheitsforschung Dependency Confusion. Darin beschreibt er, wie ein neuartiger Supply-Chain-Angriff, der Fehlkonfigurationen von Entwicklern sowie Designfehler zahlreicher Paketmanager in Open-Source-Software-Ökosystemen mit Programmiersprachen ausnutzt, ihm Zugriff auf Daten von Unternehmen wie Yelp, Tesla, Apple, Microsoft und weiteren verschaffte, die er anschließend exfiltrierte.
Diese Sicherheitsforschung erregte viel Aufmerksamkeit und führte zu einer neuen Vielfalt an Tools, mit denen Unternehmen feststellen können, ob sie anfällig für potenzielle Dependency-Confusion-Angriffe sind.
In diesem Artikel führe ich Sie Schritt für Schritt in den Dependency-Confusion-Angriff ein und zeige, wie er sich für JavaScript- und Node.js-Entwickler im npm-Ökosystem äußert. Außerdem werfen wir einen Blick auf ein neues Open-Source-Tool von Snyk, mit dem Sie potenziell gefährliche Auswirkungen von Dependency Confusion in Ihren eigenen Quellcode-Repositories erkennen können: snync.
Pop-Quiz! Können Sie erraten, wofür snync steht? Die Antwort finden Sie am Ende …
In diesem Artikel erfahren Sie, auf welche Arten sich dieser Supply-Chain-Angriff äußert und wie Sie die einzelnen Varianten eindämmen können:
Fehlkonfiguration privater npm-Registries
Private npm-Registries rufen die neuesten Versionen ab
Manuelle Paketaktualisierungen können schädliche Versionen einführen
Sind Sie von Dependency-Confusion-Angriffen betroffen?
Dependency-Confusion-Angriffe funktionieren nur bei Unternehmen, die auf interne Quellcode-Bibliotheken angewiesen sind. Private Pakete zu verwalten, um geistiges Eigentum zu schützen, ist sehr üblich. Deshalb nutzen viele Unternehmen interne Proxys, Caches oder private Paket-Hosting-Registry-Dienste.
Wenn ein Unternehmen ein internes privates Paket verwaltet, existiert dieses Paket (per Definition) nicht in öffentlichen Registries und deren Spiegeln. Da das private Paket nicht in einer öffentlichen Registry gelistet ist, kann jede andere Person den Paketnamen reservieren und möglicherweise einen Dependency-Confusion-Angriff gegen Sie starten. Dass ein privates Paket denselben Namen wie ein öffentliches Paket haben kann, ist der Kern dieses Angriffs.
In den JavaScript- und Node.js-Ökosystemen lässt sich die Angriffsfläche für Dependency Confusion erheblich reduzieren, wenn Sie Scoped Packages als reservierten Namensraum verwenden. Beachten Sie jedoch: Unabhängig davon, ob Sie npm oder yarn verwenden, sind Sie für diesen Supply-Chain-Angriff anfällig.
Dependency-Confusion-Angriffe nachstellen
Um Supply-Chain-Angriffe in Form von Dependency Confusion praktisch zu untersuchen, führen wir ein praktisches Tutorial durch, das die Schwachstelle und Möglichkeiten zu ihrer Behebung veranschaulicht.
Das folgende Paketmanifest gehört zu einem intern als Death Star bekannten Projekt. Ein großartiger Name, finden Sie nicht?
Dieser Code befindet sich in der Datei package.json und zeigt außerdem die Abhängigkeiten dieses Projekts. Wie Sie sehen, ist superlaser ein abhängiges Paket. Natürlich handelt es sich dabei um eine streng geheime Waffe des Imperiums, die daher nur intern veröffentlicht wird. Der Death Star muss im Weltraum mobil sein und ist deshalb auch auf das private Paket death-star-secret-hyper-matter-reactor angewiesen.
So richten Sie eine private npm-Registry ein
Wenn Sie zu Hause mitmachen möchten, müssen wir kurz vom Hauptthema des Artikels abweichen und eine private npm-Registry einrichten. Damit können Sie selbst experimentieren und diesen Dependency-Confusion-Supply-Chain-Angriff von Anfang bis Ende nachstellen.
Dazu richten wir mit Verdaccio, einem Open-Source-Projekt für diesen Zweck, eine interne private npm-Registry und einen Proxy-Server ein. Wenn Docker installiert ist, können wir diese ganz einfach wie folgt starten:
Wenn alles funktioniert, begrüßt uns Verdaccio:

Glückwunsch! Sie hosten jetzt Ihre eigenen npm-Pakete lokal und exklusiv auf Port 4873.
Veröffentlichen wir nun unsere geheimen internen npm-Pakete superlaser und death-star-secret-hyper-matter-reactor. Zunächst fügen wir dieser neuen privaten Verdaccio-Registry einen Benutzer hinzu:
Als Nächstes folgt die Manifestdatei unseres superlaser-Pakets: package.json:
Das Paketmanifest für death-star-secret-hyper-matter-reactor ist ähnlich, nur der Paketname ist anders. Veröffentlichen wir es nun in unserer privaten npm-Registry:
Warum gibt es Dependency Confusion?
Es gibt im Wesentlichen drei Fälle, die zu dieser Art von Software-Supply-Chain-Angriff führen können:
Fehlkonfiguration auf einem Entwickler- oder Testserver
Neuere Paketversionen, die in der öffentlichen npm-Registry veröffentlicht wurden
Mögliche Designfehler in Paketmanagern
Sehen wir uns diese Fälle genauer an.
Fehlkonfiguration privater npm-Registries
Wenn ein Entwickler oder ein CI-System (Continuous Integration) den Quellcode des Projekts the-death-star project klont, das die interne Abhängigkeit superlaser enthält, wie wird diese Abhängigkeit abgerufen?
Beim Aufruf des Befehls npm install müssen wahrscheinlich folgende Voraussetzungen erfüllt sein:
Die URL der privaten npm-Registry, in der sich dieses interne Paket befindet, wird benötigt.
Für den Zugriff auf diese private Registry wird ein Token oder eine andere Art von Zugangsdaten benötigt.
Beim ersten oben beschriebenen Schritt kann etwas schiefgehen. Um eine bestimmte private npm-Registry anzugeben, müssen Sie dem npm-Paketmanager explizit Konfigurationsinformationen bereitstellen.
Sehen wir uns nun einige Szenarien an:
Was passiert, wenn im CI-System die private Registry nicht festgelegt ist?
Was passiert, wenn Sie neu zu einem bestehenden Projekt dazukommen und zuvor keine Schritte wie das Ausführen des Befehls
npm config set registrydurchgeführt haben?Was passiert, wenn Sie versehentlich Ihre
.npmrc-Konfiguration entfernt oder geändert haben, sodass die interne private npm-Registry nicht mehr enthalten ist?
In all diesen Fällen, in denen die benutzerdefinierte Einstellung für eine interne Registry fehlt, greift der npm-Paketmanager standardmäßig auf die öffentliche Registry (registry.npmjs.org) zurück und lädt Pakete von dort herunter.
Jede Person kann Pakete in der öffentlichen npm-Registry veröffentlichen. Wenn also jemand mit böswilligen Absichten ein Paket namens superlaser veröffentlichen würde, würde dieses Paket anstelle Ihres internen Pakets heruntergeladen und installiert.
So schützen Sie sich vor npm-Dependency-Confusion
Das Kernproblem liegt darin, dass die Konfiguration des privaten npm-Proxys möglicherweise nicht korrekt ist. Wenn ein Entwickler oder ein CI-System diese Konfiguration nicht berücksichtigt, sind Sie potenziell gefährdet.
Der erste Schritt lautet daher: Stellen Sie immer sicher, dass eine .npmrc -Datei oder eine andere Form der Konfiguration des privaten npm-Proxys verfügbar ist.
Zweitens können Sie proaktiv erkennen, wenn Sie private Pakete verwenden, deren Namensraum in der öffentlichen npmjs-Registry nicht reserviert ist. Dafür haben wir snync entwickelt. Sie können das Tool als Teil Ihrer CI-Pipeline ausführen, bevor Sie Abhängigkeiten installieren. So schützt es Sie davor, versehentlich ein schädliches Paket zu installieren.
Im folgenden Screenshot führe ich snync über npx aus und gebe das aktuelle Verzeichnis an, in dem nach Abhängigkeiten gesucht werden soll. Außerdem lege ich fest, dass das Paket namens superlaser tatsächlich ein privates Paket ist:

Wie Sie in den Ergebnissen sehen, hat snync zwei konkrete potenzielle Fälle von Dependency Confusion bestätigt:
Das Paket
death-star-secret-hyper-matter-reactoristvulnerable, da derzeit kein Paket dieses Namens in der öffentlichen npmjs-Registry registriert ist. Das bedeutet, dass jede Person es registrieren und anschließend einen Dependency-Confusion-Angriff starten kann.Das Paket
superlaseristsuspicious. Das bedeutet, dass das Tool einen von zwei Fällen erkannt hat:Dieser Paketname wurde zuerst im Git-Quellcode eingeführt. Erst später wurde ein Paket mit demselben Namen in der öffentlichen npmjs-Registry veröffentlicht. Das bedeutet nicht, dass das öffentliche Paket in der npmjs-Registry schädlich ist, rechtfertigt aber eine Überprüfung.
Der Paketname existiert bereits in der öffentlichen npmjs-Registry, noch bevor Sie ein privates Paket mit demselben Namen erstellt haben.
snync ist ein Open-Source-Befehlszeilenprojekt auf Basis von Node.js. Wir laden Sie ein, es in Ihrer DevSecOps-Sicherheitspipeline zu verwenden.
Private npm-Registries rufen die neuesten Versionen ab
Was passiert, wenn ein gleichnamiges Paket (superlaser) in der öffentlichen npm-Registry veröffentlicht und verfügbar ist, aber eine höhere semver-Version hat?
Zur Veranschaulichung sieht die Situation folgendermaßen aus:
superlaser@1.0.0ist in der privaten npm-Registry https://localhost:4783 vorhanden.superlaser@1.99.999wurde von einem anonymen Benutzer in der öffentlichen npm-Registry unter https://www.npmjs.com/package/superlaser veröffentlicht.
Was passiert also, wenn ein neues Projekt angelegt wird und das Paket superlaser installiert werden soll? Es gibt noch keine package.json und auch keine Lockdatei (package-lock.json). Ein Entwickler beginnt einfach mit folgendem Befehl:
Bei dieser Installation könnte eine schädliche Version von superlaser installiert werden, die von einem Angreifer aus der Ferne kontrolliert wird. Aber warum? Der Entwickler hat doch die lokale npm-Registry konfiguriert.
Tests zeigen, dass sich viele dieser Proxys selbst bei konfiguriertem internem privatem npm-Proxy zunächst nach der neuesten verfügbaren Version in der öffentlichen npm-Registry erkundigen. Gibt es dort eine neuere Version, rufen die Proxys die neueste semver-Version des Pakets aus der öffentlichen Registry ab und installieren sie.
Stellen wir dieses Szenario mit Verdaccio nach. Wie Sie unten sehen, habe ich das harmlose npm-Paket superlaser in Verdaccio bereitgestellt, das mir als internes Hosting für private npm-Pakete dient:

Als Nächstes zeige ich Ihnen, wie ein npm install-Befehl in einem neuen Projektverzeichnis, das nur die auf die lokale Verdaccio-Registry verweisende Datei .npmrc enthält, die neueste Version des Pakets superlaser aus der öffentlichen npm-Registry abruft. Dabei hatte ich erwartet, einfach superlaser@1.0.0 zu erhalten, das ich intern veröffentlicht habe:

Das führt zu einem unerwarteten Ergebnis und kann Endbenutzer potenziell gefährden.
Zu diesem Verhalten gibt es eine öffentliche Diskussion im Open-Source-Projekt Verdaccio auf GitHub, falls Sie sich beteiligen und das Thema weiterverfolgen möchten.
Technisch gesehen berücksichtigt dieser Prozess von Verdaccio und anderen privaten npm-Proxys mehrere Variablen, zum Beispiel:
Die neueste semver-Version
Den Zeitpunkt der Paketveröffentlichung
Wenn beispielsweise in der öffentlichen npmjs-Registry eine hohe semver-Version vorhanden ist, ein gleichnamiges Paket mit einer niedrigeren semver-Version (aus demselben Versionsbereich wie das öffentliche Paket) jedoch nach der Veröffentlichung der höheren Version erstellt wird, ruft Verdaccio das Paket aus der öffentlichen Registry nicht ab.
So verhindern Sie, dass das falsche Paket abgerufen wird
Konfigurieren Sie Ihren privaten npm-Proxy so, dass er Anfragen niemals an öffentliche Registries weiterleitet. Ist ein Paket oder eine Version lokal nicht verfügbar, sollte es auf eine Weise aufgelöst werden, bei der Pakete nicht blind aus nicht vertrauenswürdigen und ungeprüften Quellen abgerufen werden.
Wenn Sie wie in unseren Beispielen Verdaccio verwenden, können Sie dies mit der folgenden Konfiguration in /verdaccio/conf/config.yaml einrichten:
Oben sehen Sie die standardmäßige Verdaccio-Konfigurationsdatei für die Docker-Container-Version. Sie können jedoch den Kommentar # DO NOT FETCH PACKAGES FROM NPMJS finden, der in der folgenden Zeile die Option proxy: npmjs auskommentiert. Dadurch wird verhindert, dass Verdaccio für Pakete, auf die das entsprechende Muster zutrifft, etwas von npmjs abruft.
Manuelle Paket-Updates können schädliche Versionen einführen
In diesem Szenario aktualisieren Sie Ihre npm-Pakete manuell, indem Sie npm update odernpm install <packages>@latest ausführen, um Ihre Abhängigkeiten auf den neuesten Stand zu bringen.
Wenn Sie diese Update-Verfahren ausführen, tritt hier dasselbe Verhalten auf wie zuvor. Der Befehl npm update weist den privaten npm-Proxy an, die neueste Version abzurufen. Daraufhin prüft der Proxy, welche Version im öffentlichen npm-Registry am aktuellsten ist.
Hinweis: Wenn Sie Yarn verwenden, führt der Befehl yarn upgrade zum selben Ergebnis: Er ruft potenziell schädliche Pakete aus dem öffentlichen npmjs-Registry ab.
Das lässt sich anhand des folgenden Szenarios veranschaulichen. Wir beginnen mit der internen Version superlaser@1.0.0:
Ich habe zwar eine .npmrc-Datei, in der die lokale Registry festgelegt ist und die auf den laufenden Verdaccio-Server verweist. Wenn ich jedoch einfach npm update ausführe, um alle Abhängigkeiten auf den neuesten Stand zu bringen, sehen Sie, dass die neueste semver-kompatible Version aus dem öffentlichen npmjs-Registry abgerufen wird:

Wie können Sie sich davor schützen?
Anstatt npm-Pakete manuell und ohne Prüfung zu aktualisieren, sollten Sie automatische Paket-Updates nutzen, die als Pull Requests an Ihre Open-Source-Projekt-Repositories gesendet werden. Dabei wird auch das Paketmanifest synchronisiert, etwa package-lock.json oder yarn.lock.
Mit Snyk können Sie npm-Paket-Updates automatisieren – und zwar kostenlos. Der folgende Screenshot zeigt einen zusammengeführten Pull Request:

Sie können die Einstellungen für automatische Updates weiter anpassen, zum Beispiel die Anzahl der von Snyk erstellten Pull Requests begrenzen oder bestimmte Pakete vollständig von Updates ausschließen. Weitere Informationen finden Sie in der Snyk-Dokumentation zum Aktualisieren von Abhängigkeiten mit automatischen PRs.
Starten Sie mit Capture-the-Flag
Erfahren Sie in unserem virtuellen On-Demand-Workshop für Einsteiger, wie Sie Capture-the-Flag-Challenges lösen.
Zum Abschluss: Ressourcen zur Anwendungssicherheit
Wenn Sie bis hierher gelesen haben, haben Sie sich die Antwort auf das Quiz verdient, das wir zu Beginn des Artikels gestellt haben. Wir haben das Open-Source-Tool snync als Abkürzung für So Now You’re Not Confused (also „Jetzt sind Sie nicht mehr verwirrt“) benannt. Haben Sie es erraten?
Sichere Methoden sind heutzutage angesichts von Angriffen auf die Supply-Chain-Sicherheit unverzichtbar – sowohl beim Schreiben von Code als auch bei der täglichen Sicherheitsroutine von Entwicklerinnen und Entwicklern. Wenn Sie oder Ihr Team regelmäßig mit JavaScript oder Node.js arbeiten, sind diese Ressourcen hilfreich:


