Researchers from the Helmholtz Center for Information Security (CISPA) and the Royal Institute of Technology (Sweden) analyzed the applicability of the prototype pollution technique for launching attacks on the Node.js platform and popular applications built on it, leading to code execution.
The prototype pollution method exploits a feature of JavaScript that allows adding new properties to the root prototype of any object. Applications may contain code blocks (gadgets) that are affected by an injected property; for example, the code may contain a construct like ‘const cmd = options.cmd || "/bin/sh"’, the logic of which will change if an attacker succeeds in injecting the ‘cmd’ property into the root prototype.
To successfully execute an attack, it is required that the incoming data in the application can be used to create a new property in the root prototype of an object, and also that a gadget dependent on the modified property is encountered during execution. The prototype modification is facilitated by the handling of the special properties ‘__proto__’ and ‘constructor’ in Node.js. The ‘__proto__’ property returns the class prototype of the object, while the ‘constructor’ property returns the function used to create the object.
If the application code contains an assignment ‘obj[a][b] = value’ and values are set from external data, the attacker can set ‘a’ to ‘__proto__’ and achieve the installation of their property with the name ‘b’ and value ‘value’ in the root prototype of the object (obj.__proto__.b = value;), with the installed property in the prototype being visible in all objects. Similarly, if the code contains expressions like ‘obj[a][b][c] = value’, setting ‘a’ to ‘constructor’ and ‘b’ to ‘prototype’ can define a new property with the name ‘c’ and value ‘value’ across all existing objects.
Example of prototype modification: const o1 = {}; const o2 = new Object(); o1.__proto__.x = 42; // creating the property ‘x’ in the root prototype console.log(o2.x); // accessing the property ‘x’ from another object // will output 42, as the root prototype used by the o2 object was modified through o1.
Example of vulnerable code: function entryPoint(arg1, arg2, arg3) { const obj = {}; const p = obj[arg1]; p[arg2] = arg3; return p; }
If the arguments of the entryPoint function are formed from input data, an attacker can pass the value "__proto__" to arg1 and create a property with any name in the root prototype. If arg2 is set to "toString" and arg3 to 1, it is possible to define the property "toString" (Object.prototype.toString = 1), causing the application to crash when calling the toString() function.
As an example of situations that may lead to the execution of the attacker's code, properties such as "main", "shell", "exports", "contextExtensions", and "env" can be created. For example, an attacker can create a property "main" in the root prototype of an object by writing the path to their script (Object.prototype.main = "./../../pwned.js"), and this property will be called during the execution of the require("my-package") code block, if the included package does not explicitly define the "main" property in package.json (if the property is not defined, it will be obtained from the root prototype). Similarly, properties "shell", "exports", and "env" can be substituted: 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");
Researchers analyzed 10,000 NPM packages with the highest number of dependencies and found that 1,958 of them do not have a "main" property in package.json, 4,420 use relative paths in require expressions, and 355 directly use the API for command substitution.
As a working example, an exploit for attacking the Parse Server backend, which overrides the evalFunctions property, can be mentioned. To simplify the identification of such vulnerabilities, a toolkit has been developed that combines methods of static and dynamic analysis. During testing of Node.js, 11 gadgets were identified that can be used to organize attacks that lead to the execution of the attacker's code. In addition to Parse Server, two exploitable vulnerabilities were also found in NPM CLI.
Source: opennet.ru
