NodeJS - __proto__ & prototype Pollution
Last updated
Last updated
First of all, we need to understand Object
in JavaScript. An object is simply a collection of key and value pairs, often called properties of that object. For example:
In Javascript, Object
is a basic object, the template for all newly created objects. It is possible to create an empty object by passing null
to Object.create
. However, the newly created object will also have a type that corresponds to the passed parameter and inherits all the basic properties.
Previously we learned that an Object in javascript is collection of keys and values, so it makes sense that a null
object is just an empty dictionary: {}
In Javascript, the concepts of the class and the function are quite interrelated (the function itself acts as the constructor for the class and the actual nature has no concept of “class” in javascript). Let’s see the following example:
One thing to note is that the prototype attribute can be changed/modified/deleted when executing the code. For example functions to the class can be dynamically added:
Functions of the class can also be modified (like toString
or valueOf
the following cases):
In a prototype-based program, objects inherit properties/methods from classes. The classes are derived by adding properties/methods to an instance of another class or by adding them to an empty object.
Note that, if you add a property to an object that is used as the prototype for a set of objects (like the myPersonObj), the objects for which it is the prototype also get the new property, but that property is not printed unless specifically called on.
You should have already learned that every object in JavaScript is simply a collection of key and value pairs and that every object inherits from the Object type in JavaScript. This means that if you are able to pollute the Object type each JavaScript object of the environment is going to be polluted!
This is fairly simple, you just need to be able to modify some properties (key-value pairs) from and arbitrary JavaScript object, because as each object inherits from Object, each object can access Object scheme.
From the previous example it's possible to access the structure of Object using the following ways:
So, as it was mentioned before, if now a property is added to the Object scheme, every JavaScript object will have access to the new property:
So now each JS object will contain the new properties: the function printHello
and the new constant globalconstant
This technique isn't as effective as the previous one as you cannot pollute the scheme of JS Object. But in cases where the keyword __proto__
is forbidden this technique can be useful.
If you are able to modify the properties of a function, you can modify the prototype
property of the function and each new property that you adds here will be inherit by each object created from that function:
In this case only the objects created from the person
class will be affected, but each of them will now inherit the properties sayHello
and newConstant
.
There are 2 ways to abuse prototype pollution to poison EVERY JS object.
The first one would be to pollute the property prototype of Object (as it was mentioned before every JS object inherits from this one):
If you manage to do that, each JS object will be able to execute the function sayBye
.
The other way is to poison the prototype of a constructor of a dictionary variable like in the following example:
After executing that code, each JS object will be able to execute the function sayHey
.
So where’s the prototype pollution? It happens when there’s a bug in the application that makes it possible to overwrite properties of Object.prototype
. Since every typical object inherits its properties from Object.prototype
, we can change application behaviour. The most commonly shown example is the following:
Imagine that we have a prototype pollution that makes it possible to set Object.prototype.isAdmin = true
. Then, unless the application explicitly assigned any value, user.isAdmin
is always true!
For example, obj[a][b] = value
. If the attacker can control the value of a
and value
, then he only needs to adjust the value of a
to __proto__
(in javascript, obj["__proto__"]
and obj.__proto__
are completely equivalent) then property b
of all existing objects in the application will be assigned to value
.
However, the attack is not as simple as the one above, according to paper, we can only attack when one of the following three conditions is met:
Perform recursive merge
Property definition by path
Clone object
Imagine a real JS using some code like the following one:
You can observe that the merge function is coping one by one all the key-value pairs from a dictionary into another one. This may seem secure, but it isn't as the copy of the __proto__
or prototype
properties from a dictionary into an object may modify completely the structure of the rest of the JS objects (as it was previously explained).
This trick was taken from https://research.securitum.com/prototype-pollution-rce-kibana-cve-2019-7609/.
Basically, if a new process using node is spawned and you are able to poison the environmental variables it's possible to execute arbitrary commands.
It's also possible to poison environmental variables y setting the env
property in some object inside JS.
For more information about why this works read the previously indicated URL.
You can poison all the objects env
property abusing __proto__
:
Or all the objects abusing prototype
from a dictionary constructor
:
Executing any of the last 2 chunks of code (and creating some VersionCheck.js
file) the file /tmp/hacktricks
is going to be created.
Going back to the initial example if you substitute the USERINPUT
with the following line arbitrary command execution will be achieved:
$ .extend, if handled incorrectly, can change the properties of the object prototype
(the template of the objects in the app). This attribute will then appear on all objects. Note that only the “deep” version (ie g) of $ .extened is affected.
Programmers often use this function to duplicate an object or fill in new properties from a default object. For example:
We can imagine myObject
is an input field from the user and is serialized into the DB)
In this code, we often think, when running will assign the attribute isAdmin
into the newly created object. But essentially, it is assigned directly to {}
and then {}.isAdmin
will be true
. If after this code, we perform the following check:
If the user has not yet existed ( undefined
), the propertyisAdmin
will be searched in its parent object, which is the Object added isAdmin
with the value true
above.
Another example when executed on JQuery 3.3.1:
These errors can affect a lot of Javascript projects, especially NodeJS projects, the most practical example is the error in Mongoose, the JS library that helps manipulate MongoDB, in December 2018.
Lodash is also a well-known library that provides a lot of different functions, helping us to write code more conveniently and more neatly with over 19 million weekly downloads. And It got the same problem as JQuery.
CVE-2018–3721
CVE-2019–10744
This bug affects all versions of Lodash, already fixed in version 4.17.11.
In NodeJS, AST is used in JS really often, as template engines and typescript etc. For the template engine, the structure is as shown above.
Info taken from https://blog.p6.is/AST-Injection/
You can insert any string into Object.prototype.pendingContent
to determine the possibility of an attack.
This allows you to be sure that servers are using handlebars engine when a prototype pollution exists in a black-box environment.
This is done by the appendContent
function of javascript-compiler.js
appendContent
is this.If pendingContent
is present, append to the content and returns.
pushSource
makes the pendingContent
to undefined
, preventing the string from being inserted multiple times.
Handlebars work as shown in the graph above.
After lexer and parser generater AST, It passes to compiler.js
We can run the template function compiler generated with some arguments. and It returns the string like “Hello posix” (when msg is posix)
The parser in handlebars forces the value of a node whose type is NumberLiteral to always be a number through the Number constructor. However, you can insert a non-numeric string here using the prototype pollution.
First, look at the compile function, and it supports two ways of input, AST Object and template string.
when input.type is a Program
, although the input value is actually string.
Parser considers it’s already AST parsed by parser.js and send it to the compiler without any processing.
The compiler given the AST Object (actually a string) send it to the accept
method.
and accept
calls this[node.type]
of Compiler.
Then take body attribute of AST and use it for constructing function.
As a result, an attack can be configured like this. If you have gone through parser, specify a string that cannot be assigned to the value of NumberLiteral. But Injected AST processed, we can insert any code into the function.
https://github.com/hughsk/flat/issues/105
More info in https://blog.p6.is/AST-Injection/#Pug
Freeze properties with Object.freeze (Object.prototype)
Perform validation on the JSON inputs in accordance with the application’s schema
Avoid using recursive merge functions in an unsafe manner
Use objects without prototype properties, such as Object.create(null)
, to avoid affecting the prototype chain
Use Map
instead of Object
Regularly update new patches for libraries