Skip to main content

Dependency-Confusion-Angriffe auf npm erkennen und verhindern, um die Supply-Chain-Sicherheit zu gewährleisten

Artikel von

13. September 2021

0 Min. Lesezeit

Am 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?

{
  "name": "the-death-star",
  "version": "1.0.0",
  "main": "index.js",
  "scripts": {
    "start": "node server.js",
  },
  "license": "ISC",
  "dependencies": {
    "debug": "^4.3.2",
    "death-star-secret-hyper-matter-reactor": "^1.0.0",
    “superlaser”: “^1.0.0”
  }
}

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:

docker run -it --rm --name verdaccio -p 4873:4873 verdaccio/verdaccio\

Wenn alles funktioniert, begrüßt uns Verdaccio:

Terminalausgabe mit einem Docker-Befehl, der Verdaccio auf Port 4873 startet, geladenen Plugins und der angezeigten HTTP-Adresse

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:

npm adduser --registry https://localhost:4873

Als Nächstes folgt die Manifestdatei unseres superlaser-Pakets: package.json:

{
  "name": "superlaser",
  "version": "1.0.0",
  "description": "Our secrets weapon",
  "main": "index.js",
  "scripts": {
    "start": "node index.js",
  },
  "license": "ISC"
}

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:

npm publish  --registry https://localhost:4873

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:

  1. Die URL der privaten npm-Registry, in der sich dieses interne Paket befindet, wird benötigt.

  2. 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:

  1. Was passiert, wenn im CI-System die private Registry nicht festgelegt ist?

  2. Was passiert, wenn Sie neu zu einem bestehenden Projekt dazukommen und zuvor keine Schritte wie das Ausführen des Befehls npm config set registry durchgeführt haben?

  3. 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:

Terminalausgabe mit einem Snyk-Scan von Abhängigkeiten, von denen eine als anfällig und eine weitere als verdächtig gekennzeichnet ist.

Wie Sie in den Ergebnissen sehen, hat snync zwei konkrete potenzielle Fälle von Dependency Confusion bestätigt:

  1. Das Paket death-star-secret-hyper-matter-reactor ist vulnerable, 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.

  2. Das Paket superlaser ist suspicious. Das bedeutet, dass das Tool einen von zwei Fällen erkannt hat:

    1. 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.

    2. 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:

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:

npm install superlaser

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:

Dunkle Verdadccia-Browserseite mit dem Paket „superlaser“, Version 1.0.0, Veröffentlichungsdetails und Anmeldeoptionen

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:

Terminal mit einer npm-Installation von superlaser, einer postinstall-Meldung und einer Warnung für das Paket the-death-star.

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:

  1. Die neueste semver-Version

  2. 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:

#
# This is the config file used for the docker images.
# It allows all users to do anything, so don't use it on production systems.
#
# Do not configure host and port under `listen` in this file
# as it will be ignored when using docker.
# see https://verdaccio.org/docs/en/docker#docker-and-custom-port-configuration
#
# Look here for more config file examples:
# https://github.com/verdaccio/verdaccio/tree/master/conf
#

# path to a directory with all packages
storage: /verdaccio/storage/data
# path to a directory with plugins to include
plugins: /verdaccio/plugins

web:
  # WebUI is enabled as default, if you want disable it, just uncomment this line
  #enable: false
  title: Verdaccio
  # comment out to disable gravatar support
  # gravatar: false
  # by default packages are ordercer ascendant (asc|desc)
  # sort_packages: asc
  # darkMode: true

# translate your registry, api i18n not available yet
# i18n:
# list of the available translations https://github.com/verdaccio/ui/tree/master/i18n/translations
#   web: en-US

auth:
  htpasswd:
    file: /verdaccio/storage/htpasswd
    # Maximum amount of users allowed to register, defaults to "+infinity".
    # You can set this to -1 to disable registration.
    # max_users: 1000

# a list of other known repositories we can talk to
uplinks:
  npmjs:
    url: https://registry.npmjs.org/

packages:
  '@*/*':
    # scoped packages
    access: $all
    publish: $authenticated
    unpublish: $authenticated
    # DO NOT FETCH PACKAGES FROM NPMJS
    #proxy: npmjs

  '**':
    # allow all users (including non-authenticated users) to read and
    # publish all packages
    #
    # you can specify usernames/groupnames (depending on your auth plugin)
    # and three keywords: "$all", "$anonymous", "$authenticated"
    access: $all

    # allow all known users to publish/publish packages
    # (anyone can register by default, remember?)
    publish: $authenticated
    unpublish: $authenticated

    # if package is not available locally, proxy requests to 'npmjs' registry
    # DO NOT FETCH PACKAGES FROM NPMJS
    #proxy: npmjs

middlewares:
  audit:
    enabled: true

# log settings
logs:
  - { type: stdout, format: pretty, level: http }
  #- {type: file, path: verdaccio.log, level: info}
#experiments:
#  # support for npm token command
#  token: false
#  # support for the new v1 search endpoint, functional by incomplete read more on ticket 1732
#  search: false

# This affect the web and api (not developed yet)
#i18n:
#web: en-US

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:

{
  "name": "new-project",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "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:

Terminalausgabe mit npm update, das superlaser@1.99999.999 installiert und eine postinstall-Meldung sowie Prüfergebnisse anzeigt

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:

GitHub-Pull-Request, in dem Snyk node-uuid von 1.4.0 auf 1.4.8 aktualisiert, um ein Sicherheitsproblem mittlerer Schwere bei einem unsicheren Vektor zu beheben.

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:

  1. 10 Best Practices für GitHub-Sicherheit

  2. 10 Best Practices für npm-Sicherheit

  3. 10 Best Practices für die Containerisierung von Node.js-Webanwendungen mit Docker

Weiterlesen

Blog

Frontier-Modelle fanden die Schwachstellen. Nur der Angreifer fand die Angriffsketten.

Die statische Analyse fand die Schwachstellen, doch erst Live-Angriffstests bewiesen, wie sie sich zu Angriffen verketten lassen. Ein Vergleich von Evo COS, Claude Security und Claude Code Security.

feature insights context
Blog

Autonome Angriffe sind bereits Realität. Die Verteidigung muss Schritt halten.

Autonome Angreifer verkürzen das Zeitfenster für die Verteidigung. Erfahren Sie, wie kontinuierliches Erkennen, Beheben, Validieren und Verhindern Sicherheitsteams helfen kann, Schritt zu halten.

Blog

Warum KI-Coding-Agenten immer wieder fehlerhafte Zugriffskontrollen schreiben

KI-Coding-Agenten können Autorisierungslogik erzeugen, die kompiliert und die Prüfung besteht, dabei aber die Daten eines Mandanten für einen anderen offenlegt. Erfahren Sie, warum fehlerhafte Zugriffskontrollen schwer zu erkennen sind und wie Sie sie verhindern.