Angriff auf Node.js ĂŒber Manipulationen an JavaScript-Objektprototypen

Forscher des Helmholtz-Zentrums fĂŒr Informationssicherheit (CISPA) und des Royal Institute of Technology (Schweden) haben die Anwendbarkeit der Technik der Prototyp-Verschmutzung von Objekten in JavaScript fĂŒr die Erstellung von Angriffen auf die Node.js-Plattform und beliebte Anwendungen auf dieser Basis analysiert, die zur CodeausfĂŒhrung fĂŒhren.

Die Methode der Prototyp-Verschmutzung nutzt eine Eigenschaft der Programmiersprache JavaScript, die es ermöglicht, neue Eigenschaften zum Root-Prototyp jedes Objekts hinzuzufĂŒgen. In Anwendungen können Codeblöcke (Widgets) vorhanden sein, deren FunktionalitĂ€t von einer eingefĂŒgten Eigenschaft abhĂ€ngt; zum Beispiel kann im Code eine Konstruktion wie 'const cmd = options.cmd || "/bin/sh"' vorkommen, deren Logik sich Ă€ndern wird, wenn ein Angreifer in der Lage ist, die Eigenschaft "cmd" im Root-Prototyp einzufĂŒgen.

FĂŒr einen erfolgreichen Angriff ist es erforderlich, dass die von außen kommenden Daten in der Anwendung verwendet werden können, um eine neue Eigenschaft im Root-Prototyp eines Objekts zu erstellen, sowie dass im Verlauf der AusfĂŒhrung ein Gadget auftritt, das von der geĂ€nderten Eigenschaft abhĂ€ngt. Die Änderung des Prototyps erfolgt durch die Bearbeitung der speziellen 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 Anwendungscode eine Zuweisung wie "obj[a][b] = value" vorkommt und die Werte aus Ă€ußeren Daten stammen, kann der Angreifer "a" auf "__proto__" setzen und erreichen, dass seine Eigenschaft mit dem Namen "b" und dem Wert "value" im Root-Prototyp des Objekts festgelegt wird (obj.__proto__.b = value;), wobei die im Prototyp festgelegte Eigenschaft in allen Objekten sichtbar ist. Ebenso, wenn im Code AusdrĂŒcke wie "obj[a][b][c] = value" vorkommen, kann durch Setzen von "a" auf "constructor" und "b" auf "prototype" in allen existierenden Objekten eine neue Eigenschaft mit dem Namen "c" und dem Wert "value" definiert werden.

Beispiel fĂŒr eine PrototypĂ€nderung: const o1 = {}; const o2 = new Object(); o1.__proto__.x = 42; // wir erstellen eine Eigenschaft "x" im Root-Prototyp console.log(o2.x); // wir greifen auf die Eigenschaft "x" aus einem anderen Objekt zu // als Ergebnis erhalten wir 42, da ĂŒber das Objekt o1 der Root-Prototyp geĂ€ndert wurde, der auch im Objekt o2 verwendet wird.

Beispiel fĂŒr nicht sicheren Code: function entryPoint(arg1, arg2, arg3){ const obj = {}; const p = obj[arg1]; p[arg2] = arg3; return p; }

Wenn die Argumente der Funktion entryPoint aus den Eingabedaten erstellt werden, kann der Angreifer in arg1 den Wert „__proto__“ ĂŒbergeben und eine Eigenschaft mit beliebigem Namen im Root-Prototyp erstellen. Wenn man in arg2 den Wert „toString“ und in arg3 den Wert 1 ĂŒbergibt, kann man die Eigenschaft „toString“ definieren (Object.prototype.toString=1) und einen Absturz der Anwendung wĂ€hrend des Aufrufs der Funktion toString() herbeifĂŒhren.

Beispielhafte Situationen, die zur AusfĂŒhrung von Angreifer-Code fĂŒhren können, sind die Erstellung der Eigenschaften „main“, „shell“, „exports“, „contextExtensions“ und „env“. Ein Angreifer kann beispielsweise im Root-Prototyp des Objekts eine Eigenschaft „main“ erstellen, indem er den Pfad zu seinem Skript speichert (Object.prototype.main = „.\/..\/..\/pwned.js“), und diese Eigenschaft wird beim AusfĂŒhren des Codes in der Konstruktion require(„my-package“) aufgerufen, wenn das eingebundene Paket das „main“-Attribut nicht explizit in der package.json definiert (wenn das Attribut nicht definiert ist, wird es aus dem Root-Prototyp abgerufen). Ähnlich können auch die Eigenschaften „shell“, „exports“ und „env“ eingesetzt werden: let rootProto = Object.prototype; rootProto[„exports“] = {„.“:„.\/changelog.js“}; rootProto[„1“] = „\/path\/to\/npm\/scripts\/“; \/\/ Triggeraufruf require („.\/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“; \/\/ Triggeraufruf require („bytes“);

Forscher haben 10.000 NPM-Pakete mit den meisten AbhĂ€ngigkeiten analysiert und festgestellt, dass 1958 von ihnen kein Attribut „main“ in der package.json haben, 4420 relative Pfade in der require-Anweisung verwenden und 355 direkt die API zur Einspeisung von Befehlen nutzen.

Ein funktionierendes Beispiel ist ein Exploit, der auf den Backend-Dienst Parse Server abzielt und die Eigenschaft evalFunctions ĂŒberschreibt. Zur Vereinfachung der Identifikation solcher Schwachstellen wurde ein Tool entwickelt, das statische und dynamische Analyseverfahren kombiniert. WĂ€hrend der Tests mit Node.js wurden 11 Gadgets identifiziert, die fĂŒr Angriffe verwendet werden können, die zur AusfĂŒhrung von Angreifer-Code fĂŒhren. Neben Parse Server wurden auch zwei ausnutzbare Schwachstellen im NPM CLI entdeckt.

Quelle: opennet.ru

60GB SSD 8Gb DDR4