Onderzoekers van het Helmholtz Centrum voor Informatiebeveiliging (CISPA) en het Koninklijk Technologie Instituut (Zweden) hebben de toepasbaarheid van de prototype vervuilingstechniek van JavaScript geanalyseerd om aanvallen op het Node.js-platform en populaire toepassingen ervan te creëren, wat kan leiden tot code-executie.
De prototype vervuiling methode maakt gebruik van een eigenschap van de programmeertaal JavaScript, die het mogelijk maakt om nieuwe eigenschappen toe te voegen aan het root prototype van elk object. In toepassingen kunnen codeblokken (gadgets) voorkomen die worden beïnvloed door een ingevoegd eigenschap; bijvoorbeeld kan er in de code een constructie zijn zoals ‘const cmd = options.cmd || "/bin/sh"’, waarvan de logica verandert als een aanvaller in staat is de eigenschap ‘cmd’ in het root prototype te injecteren.
Voor een succesvolle aanval is het vereist dat de van buitenaf binnenkomende gegevens in de applicatie gebruikt kunnen worden om een nieuwe eigenschap in het root prototype van een object te creëren, en dat er tijdens de uitvoering een gadget aanwezig is dat afhankelijk is van de gewijzigde eigenschap. De wijziging van het prototype vindt plaats via de verwerking van de Node.js metaproperties ‘__proto__’ en ‘constructor’. De eigenschap ‘__proto__’ retourneert het prototype van de klasse van het object, terwijl de eigenschap ‘constructor’ de functie retourneert die wordt gebruikt om het object te creëren.
Als er in de applicatiecode een toewijzing voorkomt als ‘obj[a][b] = value’ en de waarden afkomstig zijn van externe data, kan de aanvaller ‘a’ instellen op ‘__proto__’ en zijn eigenschap met de naam ‘b’ en de waarde ‘value’ in het root prototype van het object plaatsen (obj.__proto__.b = value;), waarbij de in het prototype geplaatste eigenschap zichtbaar is in alle objecten. Evenzo, als de code uitdrukkingen van de vorm ‘obj[a][b][c] = value’ tegenkomt, kan door ‘a’ in te stellen op ‘constructor’ en ‘b’ op ‘prototype’ in alle bestaande objecten een nieuwe eigenschap met de naam ‘c’ en de waarde ‘value’ worden gedefinieerd.
Voorbeeld van prototype wijziging: const o1 = {}; const o2 = new Object(); o1.__proto__.x = 42; // creëren van de eigenschap ‘x’ in het root prototype console.log(o2.x); // toegang tot de eigenschap ‘x’ vanuit een ander object // de output is 42, omdat het root prototype dat ook door o2 wordt gebruikt, is gewijzigd via object o1.
Voorbeeld van kwetsbare code: function entryPoint(arg1, arg2, arg3){ const obj = {}; const p = obj[arg1]; p[arg2] = arg3; return p; }
Als de argumenten van de functie entryPoint worden gevormd uit invoergegevens, kan een aanvaller een waarde van "__proto__" doorgeven in arg1 en een eigenschap met een willekeurige naam creëren in het hoofdschema. Als je "toString" doorgeeft in arg2 en 1 in arg3, kan je de eigenschap "toString" definiëren (Object.prototype.toString=1) en een crash van de applicatie veroorzaken tijdens het aanroepen van de functie toString().
Een voorbeeld van situaties die kunnen leiden tot de uitvoering van de code van de aanvaller, is het creëren van de eigenschappen "main", "shell", "exports", "contextExtensions" en "env". Bijvoorbeeld, de aanvaller kan in het hoofdschema van het object een eigenschap "main" creëren door het pad naar zijn script op te slaan (Object.prototype.main = "./../../pwned.js") en deze eigenschap zal worden aangeroepen tijdens de uitvoering in de codeconstructie require("my-package"), als het geïmporteerde pakket niet expliciet het eigenschap "main" in package.json definieert (als het eigenschap niet gedefinieerd is, zal het worden verkregen uit het hoofdschema). Op dezelfde manier kunnen de eigenschappen "shell", "exports" en "env" worden vervangen: let rootProto = Object.prototype; rootProto["exports"] = {".":"./changelog.js"}; rootProto["1"] = "/path/to/npm/scripts/"; // trigger call 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"; // trigger call require ("bytes");
Onderzoekers hebben 10.000 NPM-pakketten geanalyseerd met de meeste afhankelijkheden en ontdekt dat 1958 van hen geen eigenschap main hebben in package.json, 4420 gebruik maken van relatieve paden in de require-uitdrukking, en 355 rechtstreeks de API gebruiken voor het inbrengen van commando's.
Als werkend voorbeeld kan een exploit voor een aanval op de backend van Parse Server worden genoemd, dat de eigenschap evalFunctions herdefinieert. Om het opsporen van dergelijke kwetsbaarheden te vergemakkelijken, is er een toolkit ontwikkeld die statische en dynamische analysemethoden combineert. Tijdens het testen van Node.js zijn er 11 gadgets ontdekt die kunnen worden gebruikt voor aanvallen die leiden tot de uitvoering van de code van de aanvaller. Naast Parse Server zijn er ook twee exploiteerbare kwetsbaarheden ontdekt in NPM CLI.
Bron: opennet.ru
