Analysis and Exploitation of Prototype Pollution attacks on NodeJs - Nullcon HackIM CTF web 500 writeup
Feb 15, 2019 •
ctf
Prototype Pollution attacks on NodeJs is a recent research by Olivier Arteau where he discovered how to exploit an application if we can pollute the prototype of a base object.
Introduction
Prototype Pollution attacks, as the name suggests, is about polluting the prototype of a base object which can sometimes lead to RCE. This is a fantastic research done by Olivier Arteau and has given a talk on NorthSec 2018. Let’s take a look at the vulnerability in-depth with an example from Nullcon HackIm 2019 challenge named proton
:
Objects in javaScript
An object in the javaScript is nothing but a collection of key value pairs where each pair is known as a property. Let’s take an example to illustrate (you can use the browser console to execute and try it yourself):
In the above example, name
and website
are the properties of the object obj
. If you carefully look at the last statement, the console.log
prints out a lot more information than the properties we explicitly defined. Where are these properties coming from ?
Object
is the fundamental basic object upon which all other objects are created. We can create an empty object (without any properties) by passing the argument null
during object creation, but by default it creates an object of a type that corresponds to its value and inherits all the properties to the newly created object (unless its null
).
Functions/Classes in javaScript?
In javaScript, the concept of classes and functions are relative (functions itself serves as the constructor for the class and there is no explicit “classes” itself). Let’s take an example:
In the above example, we defined a function named person
and we created 2 objects named person1
and person2
. If we take a look at the properties of the newly created function and objects, we can note 2 things:
-
When a function is created, JavaScript engine includes a prototype
property to the function. This prototype property is an object (called as prototype object) and has a constructor property by default which points back to the function on which prototype object is a property.
-
When an object is created, JavaScript engine adds a __proto__
property to the newly created object which points to the prototype object of the constructor function. In short, object.__proto__
is pointing to function.prototype
.
WTH is a constructor ?
Constructor
is a magical property which returns the function that used to create the object. The prototype object has a constructor which points to the function itself and the constructor of the constructor is the global function constructor.
Prototypes in javaScript
One of the things to note here is that the prototype property can be modified at run time to add/delete/edit entries. For example:
What we did above is that we modified the function’s prototype to add a new property. The same result can be achieved using objects:
Noticied anything suspicious? We modified person1
object but why person2
also got affected? The reason being that in the first example, we directly modified person.prototype
to add a new property but in the 2nd example we did exactly the same but by using object. We have already seen that constructor returns the function using which the object is created so person1.constructor
points to the function person
itself and person1.constructor.prototype
is the same as person.prototype
.
Prototype Pollution
Let’s take an example, obj[a][b] = value
. If an attacker can control a
and value
, then he can set the value of a to __proto__
and the property b
will be defined for all existing objects of the application with the value value
.
The attack is not as simple as it feels like from the above statement. According to the research paper, this is exploitable only if any of the following 3 happens:
- Object recursive merge
- Property definition by path
- Object clone
Let’s take the Nullcon HackIM challenge to see a practical scenario. The challenge starts with iterating a MongoDB id (which was trivial to do) and we get access to the below source code:
The code starts with defining a function merge
which is essentially an insecure design of merging 2 objects. Since the latest version of libraries that does the merge() has already been patched, the challenge delibrately used the old method in which merge used to happen to make it vulnerable.
One thing we can quickly notice in the above code is the definition of 2 “admins” as const admin
and var аdmin
. Ideally javaScript doesn’t allow to define a const
variable again as var
so this has to be different. It took a good amount of time to figure out that one of them has a normal a
while the other has some other a
(homograph). So instead of wasting time over it, I renamed it to normal a
itself and worked on the challenge so that once solved, we can send the payload accordingly.
So from the challenge source code, here are the following observations:
Merge()
function is written in a way that prototype pollution can happen (more analysis of the same later in the article). So that’s indeed the way to solve the problem.
- The vulnerable function is actually called while hitting
/signup
via clone(body)
so we can send our JSON payload while signing up which can add the admin
property and immediately call /getFlag
to get the flag.
- As discussed above, we can use
__proto__
(points to constructor.prototype) to create the admin property with value 1.
The simplest payload to do the same: {"__proto__": {"admin": 1}}
So the final payload to solve the problem (using curl since I was not able to send homograph via burp):
Merge() - Why was it vulnerable?
One obvious question here is, what makes the merge()
function vulnerable here? Here is how it works and what makes it vulnerable:
- The function starts with iterating all properties that is present on the 2nd object
b
(since 2nd is given preference incase of same key-value pairs).
- If the property exists on both first and second arguments and they are both of type
Object
, then it recusively starts to merge it.
- Now if we can control the value of b[attr] to make attr as
__proto__
and also if we can control the value inside the proto property in b, then while recursion, a[attr]
at some point will actually point to prototype of the object a and we can successfully add a new property to all the objects.
Still confused ? Well I don’t blame, because it took sometime for me also to understand the concept. Let’s write some debug statements to figure out what is happening.
Now let’s try sending the curl request mentioned above. What we can notice is that the object b now has the value: { __proto__: { admin: 1 } }
where __proto__
is just a property name and is not actually pointing to function prototype. Now during the function merge(), for (var attr in b)
iterates through every attribute where the first attribute name now is __proto__
.
Since it’s always of type object, it starts to recursively call, this time as merge(a[__proto__], b[__proto__])
. This essentially helped us in getting access to function prototype of a
and add new properties which is defined in the proto property of b
.
References
- Olivier Arteau – Prototype pollution attacks in NodeJS applications
- Prototypes in javaScript
- MDN Web Docs - Object
Anirudh Anand
Product Security ♥ | CTF - @teambi0s | Security Trainer - @7asecurity | certs - eWDP, OSCP, OSWE