Gezielter npm-Dependency-Confusion-Angriff auf frischer Tat ertappt
Snyk Security Research Team
30. April 2022
0 Min. LesezeitZeitlicher Ablauf der Angriffsaufklärung
10. Mai 2022: CodeWhite, ein Red-Team-Sicherheitsunternehmen, kontaktierte uns auf Twitter und übernahm die Verantwortung für die schädlichen Pakete. Das Unternehmen erklärte, sie seien Teil einer Angriffssimulation für seine Kunden gewesen – Hut ab vor diesem ausgeklügelten Angriff!
4. Mai 2022: DigitalOcean meldete zurück, dass die C2-IP zu einem sicherheitsbezogenen Unternehmen gehört und man der Sache nachgehen und uns benachrichtigen werde.
1. Mai 2022: npm entfernte die schädlichen Pakete aus der Registry.
Die Geschichte einer npm-Malware
In den vergangenen Jahren haben wir einen kontinuierlichen Anstieg schädlicher Pakete in verschiedenen Ökosystemen beobachtet. Im Allgemeinen sind die allermeisten dieser Pakete harmlos: Sie sammeln Informationen, richten aber keinen Schaden auf dem infizierten Rechner an. Hin und wieder stoßen wir jedoch auf ein wirklich bösartiges Paket, das zielgerichtet, leistungsfähig und produktionsreif ist – dies ist die Geschichte eines solchen Pakets.
Echte Malware in npm finden
Das Snyk-Security-Research-Team konzentriert sich darauf, schädliche Pakete proaktiv zu erkennen. Unser wichtigstes Ziel ist es, Pakete so schnell wie möglich nach ihrem Auftauchen in der Registry eines Ökosystems zu scannen und vor ihnen zu warnen. Dafür haben wir ein robustes System mit verschiedenen Mechanismen zur Erkennung schädlicher Pakete entwickelt.
Schon kurz nachdem wir unsere Falle für schädliche Pakete aufgestellt hatten, wurden uns zahlreiche Bibliotheken als schädlich gemeldet. Bei näherer Untersuchung stellten wir jedoch fest, dass viele Meldungen sogenannte „mild schädliche“ Pakete betrafen.
Als „mild schädlich“ bezeichnen wir Pakete, die eines der folgenden Dinge tun:
Exfiltration rechnerbezogener Informationen über DNS-Abfragen (ohne weitere Aktionen)
Krypto-Miner (die zwar schlecht, aus Sicht der Malware-Forschung aber nicht besonders interessant sind)
„Mild schädliche“ Pakete anderer Forschender, die hauptsächlich zu Testzwecken dienen
Einige weitere Varianten der in dieser Liste aufgeführten Punkte
Schließlich stießen wir auf ein sehr interessantes Paket – gxm-reference-web-auth-server – und stuften es als schädlich ein. Wir sahen es uns kurz an und bemerkten, dass etwas anders war. Also starteten wir eine VM und untersuchten das Tar-Archiv.
Da sich die Meldung auf ein npm-Paket bezog, sahen wir uns zuerst die Datei `package.json` an. Wie erwartet enthielt sie ein Post-Install-Skript, das eine JavaScript-Datei aus dem Paket aufrief. Falls Ihnen das nicht geläufig ist: Post-Install-Skripte sind eine sehr verbreitete Methode, Skripte auszuführen, sobald npm die entsprechenden Abhängigkeiten installiert hat. Sie bieten außerdem eine einfache Möglichkeit für Angreifer, Skripte auf den Rechnern ihrer Opfer auszuführen.
Die aufgerufene Datei war verschleiert. Verschleierung ist jedoch nur eine vermeintlich raffinierte Art, Code zu „verstecken“. In Wirklichkeit lässt sich der Code meist relativ einfach wiederherstellen (zumindest in der JavaScript-Welt).
Als Nächstes fiel uns eine verschlüsselte Datei im Paket sofort ins Auge. Unsere Spürnasen schlugen Alarm – Zeit, der Sache auf den Grund zu gehen.
Reverse Engineering von Malware, Teil 1: der Wrapper
Wie bereits erwähnt, enthielt das Paket zwei weitere Dateien – eine verschleierte und eine verschlüsselte:
Außerdem enthielt die package.json ein Post-Install-Skript:
Zunächst konzentrierten wir uns auf das aufgerufene Skript. Dabei fielen uns auch die beiden kryptisch benannten Abhängigkeiten ldtzstxwzpntxqn und lznfjbhurpjsqmr auf, die wir später in diesem Artikel genauer untersuchen.
Als wir die Datei confsettingsaaa.js untersuchten, fanden wir Folgendes:
Um diese Datei verständlich zu machen, verwendeten wir einen Deobfuscator. Beim Durchgehen des Codes stellten wir fest, dass die Datei – im Folgenden P1 genannt, da sie Teil 1 der Malware ist – Informationen über das Betriebssystem und das Paket über gefälschte Subdomain-Abfragen an pkgio[.]com exfiltriert:
Dieser Code exfiltriert zwar lediglich Daten, und man könnte annehmen, dass er keinen Schaden anrichtet. Doch diese Exfiltrationen sind für die Aufklärung des Angreifers wertvoll, da sie ihm helfen, das Ziel und dessen Konfiguration zu identifizieren. DNS-Abfragen werden von Angreifern übrigens missbraucht, weil solche Abfragen üblicherweise Firewalls und Netzwerkfilter passieren dürfen. So können Angreifer ihre eigenen DNS-Server abfragen und die Anfragen protokollieren.
Im weiteren Verlauf der Datei wurde es interessanter. Zunächst riefen alle try/catch-Blöcke, wenn sie eine Ausnahme abfingen, eine `fail`-Funktion auf. Außerdem stellten wir fest, dass das Skript eine seiner Abhängigkeiten verwendete: lznfjbhurpjsqmr.
Bevor wir die (beiden) Abhängigkeiten erläutern, sehen wir uns zunächst die Funktion (oder den Mechanismus) fail an. Beim Aufruf tut sie im Wesentlichen zwei Dinge. Erstens beseitigt sie ihre Spuren: Die Funktion „fail“ entfernt alle zum Paket gehörenden Dateien und räumt auf:
Für Malware ist das ein raffinierter Mechanismus. Der nächste Schritt war jedoch alarmierend.
Nach dem Aufräumen erstellt die Funktion Lockvogel-package.json- und index.js-Dateien und gibt die folgende Meldung in der Konsole aus: „Bitte verwenden Sie statt des öffentlichen Repositorys die private Registry; Security Team“
Nach der Lektüre stellten sich uns einige Fragen: Warum löscht sich das schädliche Paket nicht vollständig selbst? Warum gibt es vor, ein legitimer Platzhalter eines Sicherheitsteams zu sein? Auf welche Organisation zielt dieses Paket ab?
Die meisten dieser Fragen konnten wir beantworten. Trotz aller Bemühungen, das Ziel zu identifizieren und zu warnen, konnten wir zu diesem Zeitpunkt jedoch nicht feststellen, welche Organisation angegriffen wurde. Behalten wir diese Fragen im Hinterkopf und fahren fort.
Die Abhängigkeiten: ldtzstxwzpntxqn und lznfjbhurpjsqmr
Wie oben aufgeführt, installiert das Paket zwei Abhängigkeiten: ldtzstxwzpntxqn und lznfjbhurpjsqmr. Auf ihren npm-Seiten sahen wir, dass beide vom selben Maintainer veröffentlicht wurden. So sah dessen Profil aus:

Das war interessant und bestätigte einige unserer Vermutungen über die Pakete. Die Abhängigkeiten selbst waren jedoch lediglich Kopien legitimer Pakete:
Paketname | Ursprüngliches Paket | Zweck |
|---|---|---|
|
| Paket, das eine einfachere API für npm install bereitstellt (installiert Pakete programmgesteuert). |
|
| Ermöglicht, npm global als lokales Node-Modul einzubinden. |
Wie wir später bei der Prüfung des restlichen Codes bestätigten, wurden diese Pakete tatsächlich wie die Originalpakete verwendet. Warum hat sich der Angreifer die Mühe gemacht, diese Pakete zu erstellen, statt die Originale zu nutzen? Wir wissen es nicht und werden es vermutlich nie erfahren.
Opfer filtern, private Registrys und der Übergang zu P2
Ein paar kurze Hinweise …
Wenn wir im folgenden Abschnitt schreiben, dass „der Code abbricht“, bedeutet das, dass die oben beschriebene Funktion
failzum Aufräumen aufgerufen wurde.Da sich nicht feststellen lässt, welche Organisation angegriffen wird, bezeichnen wir sie als
ORG.
Als Nächstes ruft P1 die Autorisierungskonfiguration für private Registrys aus der Datei .npmrc ab (hier kommt lznfjbhurpjsqmr zum Einsatz). Findet der Code weder eine solche Konfiguration noch die entsprechende Datei (er sucht auf dem gesamten Rechner danach), bricht er ab.
Findet der Code solche Autorisierungsinformationen, versucht er, dasselbe Paket (mit demselben Namen) aus der in der Konfigurationsdatei angegebenen privaten Registry herunterzuladen. Dabei probiert er einige Varianten aus, zum Beispiel:
Findet der Code diese Varianten nicht in der privaten Registry oder schlägt der Download fehl, bricht er ab. Kann er das Tar-Archiv aus der privaten Registry herunterladen, führt er Folgendes aus:
1. Das Modul mit npm install in einem Unterverzeichnis namens .documentation installieren (die Installation erfolgt über die Abhängigkeit ldtzstxwzpntxqn) oder abbrechen.
2. Den aktuellen Paketinhalt durch den Inhalt des neu installierten Pakets ersetzen oder abbrechen.
3. Den Inhalt zweier netzwerkbezogener Dateien sowie die neue package.json per POST-Anfrage exfiltrieren:
4. Die verschlüsselte Datei entschlüsseln und in einem neuen, abgekoppelten Prozess aufrufen (Verschlüsselungsalgorithmus, Schlüssel und IV waren fest im Code hinterlegt; mehr zu dieser Datei im nächsten Abschnitt):
5. Alle relevanten Dateien bereinigen (dabei werden nur die verschlüsselten Dateien und der P1-Code entfernt)
6. Beenden.
Daraus wird ersichtlich: Befindet sich das Paket gxm-reference-web-auth-server nicht in der privaten Registry des Ziels oder ist diese nicht erreichbar, überspringt die Malware das Ziel.
Das lässt folgende Schlüsse zu:
Das Paket zielt auf eine bestimmte Organisation ab.
Der Angreifer hinter diesen Paketen weiß, dass sich dieses Paket in der privaten Registry der Organisation befindet.
Damit sind alle Schritte von P1 abgeschlossen, und der „Malware-Wrapper“ wird beendet.
Reverse Engineering von Malware, Teil 2: der Agent
Da der Wrapper die Informationen zum Entschlüsseln der Datei fest im Code hinterlegt hatte (vermutlich ein Fehler des Angreifers), konnten auch wir sie entschlüsseln – und genau das taten wir.
Wie der Wrapper war auch diese Datei verschleiert und bestand aus einer einzigen Zeile kryptischer Zeichen. Diesmal hatte der Deobfuscator jedoch größere Schwierigkeiten und hinterließ Code voller Rätsel:
Wir probierten einige andere Deobfuscatoren aus, doch keiner lieferte ein besseres Ergebnis. Also blieben wir beim ursprünglichen Resultat und machten uns an die manuelle Entschleierung. Nach einigem Aufwand konnten wir die Datei in eine für Menschen lesbare Form bringen. Zeit, einen Blick auf die Funktionsweise des Agents zu werfen.
Agent-Registrierung
Als Erstes soll sich der Agent beim Command-and-Control-Server registrieren (bezeichnet als CNC oder C2). Dadurch erhält er drei wichtige Zeichenfolgen für die spätere Kommunikation: einen Schlüssel und einen IV zur Verschlüsselung von Payloads sowie eine UUID.
Nach Abschluss dieser Anfrage werden alle folgenden Kommunikationen zwischen dem C2-Server und dem Agent mit diesen Schlüsseln und dem IV verschlüsselt beziehungsweise entschlüsselt (der Algorithmus war fest im Code hinterlegt). Direkt nach dieser Anfrage sendet der Agent eine POST-Anfrage mit Informationen über seine Umgebung an den Server:
Nach dem Senden dieser Anfrage fährt der Agent mit dem nächsten Schritt fort.
Die Ausführungsschleife
Die Ausführungsschleife des Agents ist recht einfach aufgebaut. Sie enthält einige if-else-Abfragen für verschiedene Befehle und führt diese entsprechend den Vorgaben des C2-Servers aus. So kann der Agent sich beispielsweise selbst löschen, wenn er den Befehl delete erhält, oder einen Codeausschnitt ausführen, den der C2-Server sendet:
Zusammengefasst reagiert der Agent auf folgende Befehle:
Beachten Sie bei dieser Liste, dass exec oder eval eine Reverse Shell ausführen können, die dem Angreifer vollständige Kontrolle über den infizierten Rechner gibt. Außerdem kann die Option register bei erneutem Aufruf dem Agent und dem C2-Server helfen, ihre Verschlüsselungsinformationen auszutauschen und neue Informationen zu generieren, falls sie diese ändern möchten.
Diese Funktionen mögen einzigartig oder ausgefeilt wirken. In der Welt der C2-Agents gehören sie jedoch zur Standardausstattung eines Agents, den man auf dem Rechner eines Opfers installiert. Dennoch konnten wir den Quellcode des Agents keinem bekannten C2-Framework zuordnen.
Fazit zur Malware
Nachdem wir die Malware vollständig analysiert haben, können wir Folgendes festhalten:
Die Malware zielt auf ein einziges, bislang unbekanntes Unternehmen ab. Den Erkenntnissen aus dem Reverse Engineering zufolge dürfte dieses Unternehmen das Paket „gxm-reference-web-auth-server“ in seiner privaten Registry haben.
Da das Paket in der privaten Registry des Opfers nach sich selbst sucht, lässt sich der Angriff auch als Dependency-Confusion-Angriff einordnen – eine Form des Supply-Chain-Angriffs.
Der Angreifer oder die Angreifer verfügten wahrscheinlich über Informationen zum Vorhandensein eines solchen Pakets in der privaten Registry des Unternehmens.
Obwohl wir inzwischen genau verstanden hatten, wie das Paket funktioniert, wollten wir herausfinden, ob der C2-Server antwortet und ob es sich um eine aktive Kampagne handelt. Zeit, den Eindringling zu spielen.
„Among Us“ mit einem Angreifer spielen
Die Idee für unser nächstes Experiment war einfach. Wir wollten herausfinden, ob sich am anderen Ende der Leitung jemand befindet und, falls ja, ob die Person aktiv ist. Dazu mussten wir Folgendes tun:
Den Agenten ohne Wrapper verwenden (P1 würde unseren Client aufgrund fehlender .npmrc-Anmeldedaten herausfiltern usw.)
Den gesamten HTTP/S-Datenverkehr abfangen und protokollieren (wir wollten sehen, was der C2-Server sendet und empfängt)
Die protokollierten Daten unidirektional, unumkehrbar und nicht zurückverfolgbar an uns übertragen (wichtig, da auch der Angreifer auf die Maschine zugreifen kann!)
Doch bevor wir diese Schritte unternahmen, wollten wir einige Informationen über den Server selbst sammeln.
Ist da jemand?
Wir sammelten mithilfe standardmäßiger WHOIS- und Nmap-Scans Informationen über den Server. Die WHOIS-Ergebnisse zeigten, dass der Server bei DigitalOcean gehostet wird (dem wir die IP-Adresse des Servers gemeldet haben). Die Nmap-Scans ergaben Folgendes:
Der Server ist nicht wirklich sicher, aber aktiv und lauscht.
Der Hochstapler
Zurück zu unserem Plan: Wir mussten Folgendes vorbereiten:
Agent-Code: Diesen hatten wir aus der Entschlüsselungsphase und konnten ihn unabhängig vom Wrapper (P1) ausführen.
Interceptoren: Diese mussten in der NodeJS-Umgebung implementiert werden, da der Agent selbst die HTTP/S-Module von NodeJS verwendete.
Protokollierungs-Pipeline: Wir entschieden uns für Pipedream, da sich damit HTTP/S-Anfragen sehr granular verarbeiten lassen und bereits verschiedene Integrationen verfügbar sind (z. B. Wert-Speicherung, Slack-Integration und mehr).
Zum Abfangen verwendeten wir die Bibliothek @gr2m/http-recorder, die genau das leistete, was wir wollten: HTTP/S-Anfragen vollständig erfassen und bearbeiten.
Durch die Kombination der Schritte 1 und 2 erhielten wir folgendes Snippet:
Jetzt mussten wir es nur noch an uns senden. Mit Pipedream ließen wir die Funktion send die Nutzdaten an unseren Endpunkt übertragen, wo wir sie verarbeiten konnten.
Für die Verarbeitung speicherten wir den IV und den Schlüssel, um Nachrichten entschlüsseln zu können. Außerdem verknüpften wir die Pipedream-Pipeline mit einem anonymen Slack-Arbeitsbereich und ließen die Nachrichten dort protokollieren. So erhielten wir jedes Mal, wenn der Agent und der C2-Server Nachrichten austauschten, eine Slack-Benachrichtigung mit allen entschlüsselten und lesbaren Informationen. Unten sehen Sie eine Darstellung unseres Setups mit der infizierten Maschine:

Und hier ein Beispiel für die Ausgabe in Slack:

Und damit war unser Pseudo-Honeypot einsatzbereit.
Hallo, ich bin's
Schon kurz nachdem unser gefälschter Agent aktiv war und mit dem C2-Server kommunizierte, trafen Befehle ein. Der erste Befehl war ein ls. Anschließend versuchte der Akteur, mit cat alle sichtbaren Dateien auszulesen und das System zu durchforsten. Hier ein Beispiel für einen der empfangenen Befehle (nach der Base64-Dekodierung):

An diesem Punkt beschlossen wir, das Experiment abzubrechen, da wir unser Ziel erreicht hatten: herauszufinden, ob sich am anderen Ende jemand befand.
Das deutet auf eine aktive Kampagne gegen die Eigentümer des ursprünglichen bzw. privaten „gxm-reference-web-auth-server“ hin.
Aber warten Sie, es kommt noch mehr
Kurz bevor wir unser Experiment abschlossen, fiel uns etwas Interessantes auf. Abgesehen von der C2-Adresse gab es nur wenige weitere fest codierte Werte. Einer davon war der Wert der Eigenschaft „engine“ im initialen /register-Aufruf:
Da es uns nun egal war, unentdeckt zu bleiben, beschlossen wir, den Wert per Fuzzing zu testen, um herauszufinden, ob es noch andere Zieltypen gab. Nach einer Weile fanden wir weitere Engine-Typen:
Das deutet darauf hin, dass es mindestens einen Browser-Agenten und einen Golang-Agenten gibt. Es ist durchaus denkbar, dass es noch weitere gibt.
Offenlegung und nächste Schritte
Obwohl wir die Funktionsweise dieser Malware kennen, können wir nicht sagen, welche Organisation oder welches Unternehmen angegriffen wird. Deshalb möchten wir diese Plattform (sowie Twitter usw.) nutzen, um die Community aufzufordern, andere vor diesem Paket zu warnen und sich warnen zu lassen. Bei Fragen oder Bedenken dazu können Sie sich gern an uns wenden (oder uns eine Direktnachricht an @snyksec senden).
Wir haben uns außerdem an DigitalOcean gewandt und darum gebeten, den C2-Server aus ihrem Dienst zu entfernen. Auch npm haben wir über die schädlichen Pakete informiert und um deren Entfernung gebeten.
Damit endet unsere Untersuchung. Da Angriffe komplex und ausgeklügelt sind, möchten wir darauf hinweisen, dass der ursprüngliche Angriffsvektor eine einfache Verwechslung von Paketabhängigkeiten ist, die sich leicht entschärfen lässt. Den vollständigen Ablauf dieses Angriffs sehen Sie sich in der Abbildung unten an. Bleiben Sie wachsam und schützen Sie sich!

Sichern Sie Ihre Open-Source-Abhängigkeiten
Die entwicklerorientierten Tools von Snyk erstellen mit einem Klick Fix-Pull-Requests für anfällige Open-Source-Abhängigkeiten und deren transitive Abhängigkeiten.



