Skip to main content

Unsichere npm-Standardkonfigurationen zum Stehlen von macOS-Tastaturkurzbefehlen nutzen

Artikel von
feature open source

28. Juni 2023

0 Min. Lesezeit

Bösartige npm-Pakete und ihre Gefahren sind ein häufig diskutiertes Thema – ob es nun um Hunderte Command-and-Control-Malwarepakete mit Cobalt Strike, Typosquatting oder allgemein um im npm-Register veröffentlichte Malware geht (auch bei PyPI und anderen). Um Entwickler und Maintainer dabei zu unterstützen, sich gegen diese Sicherheitsrisiken zu wappnen, hat Snyk einen Leitfaden zu Best Practices für npm-Sicherheit veröffentlicht.

Der folgende Angriff, auf den Yagiz Nizipli langjährige Maintainer aufmerksam gemacht hat, und das reale Risiko einer Datenkompromittierung zeigen, wie wichtig es ist, das Risiko der Ausführung beliebiger Befehle mit Paketmanagern zu minimieren – etwa über npm-postinstall-Lifecycle-Hooks.

Lifecycle-Skripte von npm

Der Node Package Manager (npm) stellt Entwicklern und Paket-Maintainern eine Reihe von Skripten zur Verwaltung der Lifecycle-Ereignisse eines Pakets bereit. Diese Skripte bieten Entwicklern großen Mehrwert, da sie verschiedene Aufgaben oder Konfigurationen als Teil des Paketinstallationsprozesses ausführen können. Mit postinstall-Skripten können Entwickler beispielsweise Aufgaben wie das Erstellen von Assets, das Einrichten von Umgebungsvariablen, das Ausführen von Migrationen oder andere Aufgaben automatisieren.

Die Eigenschaft scripts einer Datei package.json legt die Befehle fest, die durch den Lifecycle des Pakets und den des von Ihnen entwickelten Pakets ausgelöst werden. Derzeit unterstützt npm in jeder Eigenschaft scripts einer Datei package.json eine begrenzte Anzahl von Lifecycle-Skripten.

Der Einfachheit halber konzentriert sich der Rest dieses Artikels auf den Befehl postinstall. Die hier beschriebenen Konzepte gelten jedoch auch für andere Lifecycle-Abläufe.

Frühere Sicherheitsvorfälle

Mehrere aufsehenerregende Vorfälle hatten konkrete Auswirkungen auf JavaScript-Entwickler, darunter:

Viele weitere Sicherheitsvorfälle rund um JavaScript und Node.js sind im Repository Awesome Node.js Security zusammengestellt.

Sicherheit gespeicherter Daten

Sicherheitsexperten bezeichnen den Schutz von Daten, die gespeichert sind oder ruhen, als Data-at-rest-Sicherheit. Damit wird sie vom Schutz während der Übertragung oder Verarbeitung abgegrenzt. Im Mittelpunkt steht der Schutz vertraulicher Informationen in Datenbanken, Dateisystemen oder persistenten Speichern. Data-at-rest-Sicherheit soll unbefugten Zugriff, Offenlegung und Manipulation verhindern, solange die Daten ruhen. Zu den Maßnahmen, mit denen sich gespeicherte Daten schützen lassen, gehören:

  • Entschlüsselung bei Bedarf: Entschlüsseln Sie nur die Daten, die für die aktuelle Aufgabe benötigt werden, und speichern Sie alle übrigen Daten verschlüsselt, um unbefugten Zugriff zu verhindern.

  • Zugriffskontrolllogik: Prüfen Sie die Identität der anfragenden Person über ein Verfahren wie ein Passwort, Zwei-Faktor-Authentifizierung oder biometrische Daten, die das Betriebssystem bereitstellt (z. B. FaceID). So lässt sich der Zugriff auf die Ressource für Unbefugte einschränken.

Die Angriffsfläche von Entwicklern

Best Practices der Branche geben vor, dass wir bei der Anwendungsentwicklung bestimmte Prinzipien befolgen. Diese Best Practices bieten viele Vorteile bei der Zusammenarbeit verschiedener Teams und Entwickler, vergrößern aber auch die Angriffsfläche.

Welche Daten liegen unverschlüsselt auf dem Rechner von Entwicklern?

  • Umgebungsvariablen in einfachen Textdateien wie .env (über das Paket dotenv verfügbar).

  • Projektkonfigurationsdateien im JSON-Format, zum Beispiel config.json.

  • SSH-Schlüssel für den Zugriff auf Github/Gitlab, die im Ordner ~/.ssh liegen.

  • Und … macOS-Tastaturkurzbefehle!

Textersetzungen unter macOS

macOS bietet standardmäßig eine Funktion namens Text Replacements, die in der Systemeinstellungen-App zu finden ist. Damit können Benutzer schnell ein Wort durch ein anderes ersetzen. Erst kürzlich habe ich erfahren, dass ein Entwickler bei einem bekannten Unternehmen Textersetzungen nutzte, um das Schlüsselwort @card durch seine Kreditkartendaten zu ersetzen. Auch wenn eine Kreditkartennummer ohne Ablaufdatum oder CVV allein keinen Zugriff auf Ihr Geld ermöglicht, schafft sie dennoch eine Angriffsfläche, die ausgenutzt werden kann.

macOS-Tastatureinstellungen mit Textersetzungen, die „@ssh-key“ „my-secret-password“ zuordnen
Die Funktion „Textersetzungen“ ist über die Systemeinstellungen unter dem Menüpunkt `Tastatur` verfügbar.

Tastatur-Textersetzungen ausleiten

Tastaturkurzbefehle werden unter defaults gespeichert, das einer dateisystemgestützten .plist-Datei in einem lokalen Ordner entspricht. Mit dem folgenden Befehl werden Ihre konfigurierten Textersetzungen ausgegeben, die auch in der Systemeinstellungen-App verfügbar sind.

Beachten Sie, dass für den folgenden Code keine sudo-Berechtigungen erforderlich sind und jeder Prozess auf Ihrem Computer ihn ausführen kann.

> defaults read -g NSUserDictionaryReplacementItems
(
  {
    on = 1;
    replace = "@ssh-key";
    with = "my-secret-password";
  }
)

Derselbe Befehl lässt sich in Node.js über execSync ausführen und ganz einfach mit dem vom Paketmanager npm unterstützten Lifecycle-Ablauf postinstall verarbeiten.

Das folgende Beispiel zeigt ein Node.js-Skript, mit dem Angreifer Textersetzungen unter macOS auslesen und vertrauliche Daten ausleiten können:

import { execSync } from 'node:child_process'

const decoder = new TextDecoder()
const res = execSync('defaults read -g NSUserDictionaryReplacementItems')
const text_replacements = decoder.decode(res)

console.log(text_replacements)

Damit der obige Code bei der Installation dieses Pakets ausgeführt wird, aktualisieren wir die Paketmanifestdatei package.json wie folgt:

{
  "name": "my-useful-library",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "type": "module",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "postinstall": "node ./retrieve.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

Wird diese Bibliothek über npm verteilt und von einem Entwickler heruntergeladen, führt sie unser benutzerdefiniertes Skript direkt aus, um die Tastatur-Textersetzungen abzurufen und zu verarbeiten. Wenn Sie nicht aufpassen, übersehen Sie leicht die Zeile mit > node ./retrieve.js.

➜  vulnerable npm i

> my-useful-library@1.0.0 postinstall
> node ./retrieve.js

up to date, audited 1 package in 192ms

found 0 vulnerabilities
➜  vulnerable

Schutzmaßnahmen

Was können Sie als Entwickler tun, um die Sicherheitsrisiken durch bösartige npm-Pakete und die allgemeinen Risiken der Ausführung beliebiger Befehle durch Pakete in Ihrem Abhängigkeitsbaum zu mindern?

Skripte bei der Installation von npm-Paketen ignorieren

Sie können sich vor Paketen schützen, die postinstall-Skripte nutzen. npm bietet beim Installieren von Paketen die Konfigurationsoption --ignore-scripts.

➜  npm i <package name> --ignore-scripts
up to date, audited 1 package in 124ms
found 0 vulnerabilities

Sichere npm-Standardeinstellungen verwenden

Der npm-Paketmanager verfügt außerdem über eine Konfigurationsdatei namens .npmrc. Sie können die Standardeinstellungen über die npm-CLI ändern, um sicherere Voreinstellungen zu gewährleisten:

➜  npm config set ignore-scripts true
➜  npm i
up to date, audited 1 package in 126ms
found 0 vulnerabilities

Sichere Speicherung

Am wichtigsten ist, dass Sie vertrauliche Informationen niemals im Klartext speichern. Wenn Sie sie aufgrund anderer Anforderungen dennoch im Klartext speichern müssen, sollten Sie den Zugriff auf die Ressource immer durch Multi-Faktor-Authentifizierung schützen.

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.