Prototype Pollution - Part 2
Part 2 of Prototype pollution
Last updated
Part 2 of Prototype pollution
Last updated
Previously we went through how objects and prototype works in JavaScript and a high level overview on how an attacker can pollute the prototype of object in JavaScript.
In order for prototype pollution vulnerability to work, the user controlled properties must be assigned to the object's prototype instead of the object itself. Hence polluting the prototype of the object. This can be done through functions like a recursive merge function.
As I mentioned in Part 1, to successfully exploit prototype pollution, you need the following key components
A source - Any user input that allows you to poison the prototypes with arbitrary properties.
A sink - A JavaScript function or DOM element that execute arbitrary code.
An exploitable gadget - A property that is passed into a sink without proper filtering.
I will be diving more in depth into these three key components
As mentioned, a prototype pollution source is any user input that enables a user to add arbitrary properties to prototype objects. These are some of the most common type of sources:
URL query via a GET request
JSON-based input via a POST request
Web messages
I will be going through URL query and JSON-based input.
One can poison the prototype via URL in such a manner:
We might think that the URL parser may interpret __proto__
as an arbitrary string. But as we mentioned before, if we merge these keys and values into an existing object as properties.
So, how did this happen? The JavaScript engine actually treat __proto__
as a getter for the prototype. As a result, the evilProperty
is assigned to the prototype rather than the target object itself. Now all objects that use the same prototype of the targetObject
will inherit the evilProperty
.
User-controllable objects are often derived from a JSON string using the JSON.parse()
method. JSON.parse()
also treats any key in the JSON object as an arbitrary string, including things like __proto__
.
Using JSON.parse()
method, if we convert the JSON object into a JavaScript object, the resulting object will have a property with the key __proto__
. If this object is merged into an existing object without proper key sanitization, it will lead to prototype pollution.
A sink refers to a point in the code where untrusted input is processed or executed. This makes it a potential risk if it is not properly handled. In our case for a web application, it will usually be a JavaScript function or DOM element that allows for arbitrary JavaScript or system commands. This obviously depends on the type of web application and the dependency libraries that are in use.
This is probably the most important part of prototype pollution as a gadget allows the vulnerability to become a viable exploit. Gadgets are usually existing function or a feature in the application that can be manipulated to trigger arbitrary execution or escalate privileges after polluting the prototype.
A property can be used as a gadget if it is:
Used by the application in an unsafe way such as passing it to a sink without proper filtering.
The object must inherit a malicious version of the property added to the prototype by the attacker.
Now for an specific example, I have a vulnerable function called compile
and I control the outputFunctionName
property of it.
Based on line 590, to make the payload work, we need to do the following:
We just need to add a x=1; + our code; + y
to the payload to execute this part properly.
If we were to have a JSON input for a API call on a /sample
endpoint, we can do the following to ensure that our code executes:
This web application will process our payload to execute the system whoami
command on the server hence allowing us to get RCE via prototype pollution.
Offsec Advanced Web Attack and Exploitation
Frost