Les chercheurs du Centre Helmholtz pour la sécurité de l'information (CISPA) et de l'Institut royal de technologie (Suède) ont analysé l'applicabilité de la technique de pollution de prototype JavaScript pour créer des attaques sur la plateforme Node.js et des applications populaires qui en dépendent, entraînant l'exécution de code.
La méthode de pollution de prototype utilise une fonctionnalité du langage JavaScript qui permet d'ajouter de nouvelles propriétés au prototype racine de tout objet. Dans les applications, il peut y avoir des blocs de code (gadgets) dont le fonctionnement est influencé par la propriété fournie. Par exemple, dans le code, on peut rencontrer une structure du type ‘const cmd = options.cmd || "/bin/sh"’, dont la logique de fonctionnement sera modifiée si un attaquant réussit à insérer la propriété « cmd » dans le prototype racine.
Pour qu'une attaque réussisse, il est nécessaire que les données entrantes dans l'application puissent être utilisées pour créer une nouvelle propriété dans le prototype racine de l'objet, et qu'un gadget dépendant de la propriété modifiée soit rencontré lors de l'exécution. La modification du prototype est réalisée grâce à la manipulation des propriétés spéciales « __proto__ » et « constructor » dans Node.js. La propriété « __proto__ » renvoie le prototype de classe de l'objet, tandis que la propriété « constructor » renvoie la fonction utilisée pour créer l'objet.
Si le code de l'application contient une assignation « obj[a][b] = value » et que les valeurs proviennent de données externes, un attaquant peut définir « a » à la valeur « __proto__ » et réussir à établir sa propriété avec le nom « b » et la valeur « value » dans le prototype racine de l'objet (obj.__proto__.b = value;), rendant ainsi la propriété établie dans le prototype visible dans tous les objets. De même, si le code contient des expressions du type « obj[a][b][c] = value », en définissant « a » à la valeur « constructor » et « b » à « prototype », il est possible de définir une nouvelle propriété avec le nom « c » et la valeur « value » dans tous les objets existants.
Exemple de modification de prototype : const o1 = {}; const o2 = new Object(); 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 // on obtiendra 42, car le prototype racine utilisé par l'objet o2 a été modifié via l'objet o1.
Exemple de code vulnérable : function entryPoint(arg1, arg2, arg3) { const obj = {}; const p = obj[arg1]; p[arg2] = arg3; return p; }
Si les arguments de la fonction entryPoint sont formés à partir des données d'entrée, un attaquant peut transmettre à arg1 la valeur «__proto__» et créer dans le prototype racine une propriété avec n'importe quel nom. En transmettant à arg2 la valeur «toString» et à arg3 la valeur 1, il est possible de définir la propriété «toString» (Object.prototype.toString=1) et de provoquer un crash de l'application lors de l'appel de la fonction toString().
À titre d'exemple de situations pouvant conduire à l'exécution de code malveillant, on peut citer la création des propriétés «main», «shell», «exports», «contextExtensions» et «env». Par exemple, un attaquant peut créer dans le prototype racine de l'objet la propriété «main», en y écrivant le chemin vers son script (Object.prototype.main = «.\/..\/..\/pwned.js») et cette propriété sera appelée lors de l'exécution du code dans la construction require(«my-package»), si le package inclus ne définit pas explicitement la propriété «main» dans le package.json (si la propriété n'est pas définie, elle sera récupérée depuis le prototype racine). De même, il est possible de substituer les propriétés «shell», «exports» et «env» : let rootProto = Object.prototype; rootProto[«exports»] = {«.»:».\/changelog.js»}; rootProto[«1»] = «\/path\/to\/npm\/scripts\/»; \/\/ déclencher l'appel 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»; \/\/ déclencher l'appel require («bytes»);
Les chercheurs ont analysé 10 000 packages NPM ayant le plus grand nombre de dépendances et ont découvert que 1958 d'entre eux n'ont pas de propriété main dans le package.json, 4420 utilisent des chemins relatifs dans l'expression require, et 355 utilisent directement l'API pour la substitution des commandes.
Un exemple de fonctionnement est un exploit pour attaquer le backend du Parse Server, qui redéfinit la propriété evalFunctions. Pour faciliter l'identification de telles vulnérabilités, un ensemble d'outils a été développé, combinant des méthodes d'analyse statique et dynamique. Au cours des tests sur Node.js, 11 gadgets ont été identifiés, pouvant être utilisés pour effectuer des attaques conduisant à l'exécution de code malveillant. Outre le Parse Server, deux vulnérabilités exploitables ont également été découvertes dans le NPM CLI.
Source : opennet.ru
