Angriff auf Node.js durch Manipulation von JavaScript-Objektprototypen

Forscher des Helmholtz-Zentrums für Informationssicherheit (CISPA) und des Königlichen Technischen Instituts (Schweden) haben die Anwendbarkeit der Technik der Prototypenverunreinigung in JavaScript zur Durchführung von Angriffen auf die Node.js-Plattform und beliebte darauf basierende Anwendungen untersucht, was zur Ausführung von Code führen kann.

Die Methode der Prototypenverunreinigung nutzt eine Besonderheit der JavaScript-Sprache, die es ermöglicht, neue Eigenschaften zum Wurzelprototyp jedes Objekts hinzuzufügen. In Anwendungen können Codeblöcke (Widgets) vorkommen, deren Funktion von einer eingefügten Eigenschaft beeinflusst wird, zum Beispiel kann der Code eine Konstruktion wie ‘const cmd = options.cmd || "/bin/sh"‘ enthalten, deren Funktionsweise sich ändert, wenn ein Angreifer die Eigenschaft „cmd“ im Wurzelprototyp einfügt.

Für einen erfolgreichen Angriff ist es erforderlich, dass die im Anwendungsprogramm von außen kommenden Daten genutzt werden können, um eine neue Eigenschaft im Wurzelprototyp des Objekts zu erstellen, und dass während der Ausführung ein Gadget auftritt, das von der geänderten Eigenschaft abhängt. Die Änderung des Prototyps erfolgt durch die Verarbeitung der internen Eigenschaften „__proto__“ und „constructor“ in Node.js. Die Eigenschaft „__proto__“ gibt den Prototyp der Objektklasse zurück, während die Eigenschaft „constructor“ die Funktion zurückgibt, die zur Erstellung des Objekts verwendet wird.

Wenn im Anwendungs-Code eine Zuweisung „obj[a][b] = value“ vorkommt und die Werte aus externen Daten stammen, kann der Angreifer „a“ auf „__proto__“ setzen und so seine eigene Eigenschaft mit dem Namen „b“ und dem Wert „value“ im Wurzelprototyp des Objekts festlegen (obj.__proto__.b = value;). Die im Prototyp festgelegte Eigenschaft ist dann in allen Objekten sichtbar. Ähnlich, wenn im Code Ausdrücke wie „obj[a][b][c] = value“ erscheinen, lässt sich durch Setzen von „a“ auf „constructor“ und „b“ auf „prototype“ in allen bestehenden Objekten eine neue Eigenschaft mit dem Namen „c“ und dem Wert „value“ bestimmen.

Beispiel für eine Änderung des Prototyps: const o1 = {}; const o2 = new Object(); o1.__proto__.x = 42; // Erstellen einer Eigenschaft „x“ im Root-Prototyp console.log(o2.x); // Zugriff auf die Eigenschaft „x“ von einem anderen Objekt // Als Ausgabe erhalten wir 42, da der Root-Prototyp durch das Objekt o1 geändert wurde, welches auch von o2 verwendet wird.

Beispiel für verwundbaren Code: function entryPoint(arg1, arg2, arg3){ const obj = {}; const p = obj[arg1]; p[arg2] = arg3; return p; }

Wenn die Argumente der Funktion entryPoint aus Eingabedaten erstellt werden, kann ein Angreifer den Wert „__proto__“ für arg1 übergeben und eine Eigenschaft mit beliebigem Namen im Root-Prototyp erstellen. Wenn man in arg2 den Wert „toString“ und in arg3 die Zahl 1 übergibt, kann man die Eigenschaft „toString“ festlegen (Object.prototype.toString=1) und einen Absturz der Anwendung während des Aufrufs der Funktion toString() verursachen.

Als Beispiel für Situationen, die zur Ausführung von Angreifercode führen können, werden die Eigenschaften „main“, „shell“, „exports“, „contextExtensions“ und „env“ genannt. Ein Angreifer könnte beispielsweise die Eigenschaft „main“ im Prototyp des Root-Objekts erstellen, indem er den Pfad zu seinem Skript speichert (Object.prototype.main = "./../../../pwned.js"). Diese Eigenschaft wird aufgerufen, wenn der Code der Konstruktion require("my-package") ausgeführt wird, es sei denn, das eingebundene Paket definiert ausdrücklich in der package.json die Eigenschaft „main“ (wenn das Attribut nicht definiert ist, wird es aus dem Root-Prototyp übernommen). Ähnlich könnten die Eigenschaften „shell“, „exports“ und „env“ eingefügt werden: let rootProto = Object.prototype; rootProto["exports"] = {".": "./changelog.js"}; rootProto["1"] = "/path/to/npm/scripts/"; // Auslösende Anrufrequire("./target.js"); Object.prototype.main = "/path/to/npm/scripts/changelog.js"; Object.prototype.shell = "node"; Object.prototype.env = {}; Object.prototype.env.NODE_OPTIONS = "--inspect-brk=0.0.0.0:1337"; // Auslösende Anrufrequire("bytes");

Forscher haben 10.000 NPM-Pakete mit den meisten Abhängigkeiten analysiert und festgestellt, dass 1958 davon kein „main“-Attribut in der package.json besitzen, 4420 relative Pfade im require-Ausdruck verwenden und 355 direkt die API zur Platzierung von Befehlen nutzen.

Ein funktionierendes Beispiel ist ein Exploit für einen Angriff auf das Backend von Parse Server, der die Eigenschaft evalFunctions überschreibt. Um solche Schwachstellen leichter zu identifizieren, wurde ein Toolkit entwickelt, das Methoden der statischen und dynamischen Analyse kombiniert. Während der Tests mit Node.js wurden 11 Gadgets identifiziert, die verwendet werden können, um Angriffe zu organisieren, die zur Ausführung von Angreifer-Code führen. Neben Parse Server wurden auch zwei exploitable Schwachstellen im NPM CLI gefunden.

Quelle: opennet.ru

Kaufen Sie zuverlässiges Hosting für Websites mit DDoS-Schutz, VPS VDS-Server 🔥 Kaufen Sie zuverlässiges Hosting für Websites mit DDoS-Schutz, VPS VDS-Server | ProHoster