Attaque sur Node.js via manipulation de prototypes d'objets JavaScript

Des chercheurs du Centre Helmholtz pour la sécurité de l'information (CISPA) et du Royal Institute of Technology (Suède) ont analysé l'applicabilité de la technique de pollution du prototype JavaScript pour créer des attaques sur la plate-forme Node.js et les applications populaires basées sur celle-ci, conduisant à l'exécution de code.

La méthode de pollution du prototype utilise une fonctionnalité du langage JavaScript qui permet d'ajouter de nouvelles propriétés au prototype racine de n'importe quel objet. Les applications peuvent contenir des blocs de code (gadgets) dont le fonctionnement est affecté par une propriété substituée ; par exemple, le code peut contenir une construction telle que « const cmd = options.cmd || "/bin/sh"', dont la logique sera modifiée si l'attaquant parvient à substituer la propriété « cmd » dans le prototype racine.

Une attaque réussie nécessite que l'application puisse utiliser des données externes pour créer une nouvelle propriété dans le prototype racine de l'objet et que l'exécution rencontre un gadget qui dépend de la propriété modifiée. La modification du prototype s'effectue en traitant les propriétés de service « __proto__ » et « constructeur » dans Node.js. La propriété "__proto__" renvoie le prototype de la classe de l'objet et la propriété "constructor" renvoie la fonction utilisée pour créer l'objet.

Si le code de l'application contient l'affectation « obj[a][b] = value » et que les valeurs sont définies à partir de données externes, un attaquant peut définir « a » sur la valeur « __proto__ » et réaliser l'installation de sa propre propriété. avec le nom « b » et la valeur « valeur » dans le prototype racine de l'objet (obj.__proto__.b = valeur ;), et la propriété définie dans le prototype sera visible dans tous les objets. De même, si le code contient des expressions telles que « obj[a][b][c] = value », en définissant « a » sur la valeur « constructeur » et « b » sur « prototype » dans tous les objets existants, vous pouvez définissez une nouvelle propriété avec le nom "c" et la valeur "value".

Exemple de changement de prototype : const o1 = {}; const o2 = nouvel objet (); o1.__proto__.x = 42 ; // crée la propriété « x » dans le prototype racine console.log (o2.x) ; // accède à la propriété "x" depuis un autre objet // le résultat sera 42, puisque le prototype racine a été modifié via l'objet o1, qui est également utilisé dans l'objet o2

Exemple de code vulnérable : function EntryPoint (arg1, arg2, arg3){ const obj = {}; const p = obj[arg1]; p[arg2] = arg3; retourner p ; }

Si les arguments de la fonction EntryPoint sont formés à partir de données d'entrée, alors un attaquant peut transmettre la valeur « __proto__ » à arg1 et créer une propriété avec n'importe quel nom dans le prototype racine. Si vous passez à arg2 la valeur "toString" et à arg3 la valeur 1, vous pouvez définir la propriété "toString" (Object.prototype.toString=1) et planter l'application lors de l'appel à toString().

Des exemples de situations pouvant conduire à l'exécution de code malveillant incluent la création des propriétés "main", "shell", "exports", "contextExtensions" et "env". Par exemple, un attaquant peut créer une propriété « main » dans le prototype racine d'un objet, en y écrivant le chemin de son script (Object.prototype.main = « ./../../pwned.js ») et cette propriété sera appelée au moment de l'exécution dans le code de la construction require("my-package"), si le package inclus ne définit pas explicitement la propriété "main" dans package.json (si la propriété n'est pas définie, il sera obtenu à partir du prototype racine). Les propriétés « shell », « exports » et « env » peuvent être remplacées de la même manière : let rootProto = Object.prototype ; rootProto["exports"] = {".":"./changelog.js"}; rootProto["1"] = "/chemin/vers/npm/scripts/"; // déclenchement de l'appel require("./target.js"); Object.prototype.main = "/path/to/npm/scripts/changelog.js"; Object.prototype.shell = "nœud" ; Objet.prototype.env = {}; Object.prototype.env.NODE_OPTIONS = "—inspect-brk=0.0.0.0:1337"; // appel déclencheur require("bytes");

Les chercheurs ont analysé 10 1958 packages NPM avec le plus grand nombre de dépendances et ont constaté que 4420 355 d’entre eux n’ont pas de propriété principale dans package.json, XNUMX XNUMX utilisent des chemins relatifs dans leurs instructions require et XNUMX utilisent directement l’API de substitution de commandes.

Un exemple fonctionnel est un exploit pour attaquer le backend du serveur Parse qui remplace la propriété evalFunctions. Pour simplifier l'identification de ces vulnérabilités, une boîte à outils a été développée qui combine des méthodes d'analyse statique et dynamique. Lors des tests de Node.js, 11 gadgets ont été identifiés pouvant être utilisés pour organiser des attaques conduisant à l'exécution du code de l'attaquant. En plus de Parse Server, deux vulnérabilités exploitables ont également été identifiées dans la CLI NPM.

Source: opennet.ru

Ajouter un commentaire