Understanding what are JavaScript Prototype Pollution (Part 2)
Let us now dig into prototype pollution more followed by its preventive measures in the code-level.
Not sure what a JavaScript object or prototype is? Check out part 1 of the article — Understanding Prototype Pollution and its Exploitation — Part 1.
Prototype pollution occurs when a user input is treated as an object and this object is cloned/copied or merged into another object.
We will take an example of the following merge function:
We define our source and target objects as:
source = {“name”:“Alex”,“age”:“27”,“State”:“Ohio”}
target = {“__proto__”:{..}}
Demystifying the merge function: The merge function is looping over all the properties in the source object (name, age, and state, as per our example) and copy the values from the source object to the target object. If one of the object properties itself is of object type, we enter the ‘if’ condition, where the the merge function is called recursively. Else, the source property is directly copied to the target property.
As per our example objects, none of the source object properties are of objects type and hence, the code does not pass the ‘if’ condition.
Now assume that an attacker sends a payload to the source object as:
source = {“name”:“Alex”,“age”:“27”,“State”:“Ohio”,“__proto__”:{“isAdmin”:true}}
Now, there is an object type property in the source object. The merge function when called, enters into the ‘if’ condition (one of the source properties is an object and target property is also an object — __proto__) thus calling the merge function to merge the source object properties into the target object recursively.
After this recursive merge, the target property becomes:
target = {“name”:“Alex”,“age”:“27”,“State”:“Ohio”,“__proto__”:{“isAdmin”:true,…}}
Adding this __proto__ property to an object pollutes the entire prototype chain and hence, all the existing and newly creating objects will have the “isAdmin” property in their prototypes. Thus, if the source object is user-controlled without any sanitizations, and this source object is directly merged or cloned to another object, then it is easy for an attacker to send payloads to pollute the global prototypes.
JSON.parse()
If the source object is user-controlled, it enters the server via the request body, which is treated as a string. Hence, the exploit will not work if the payloads are sent by the attacker directly as strings. This string needs to converted to JSON format for it to be treated as an object in the server. We use json.parse() to achieve this.
Now the attacker-controlled source object has the payload — isAdmin set to true. A new object target is created, which does not have the isAdmin property in it. Let us call the merge function with source and target objects as the function parameters.
The resultant target object now has all the properties of the source object, plus the isAdmin property set in its prototype, and is also accessible.
This type of attack based on javascript prototype pollution might cause a Property injection attack. The attacker will also be able to alter a given property or function. Denial of Service attack and Remote code execution are also possible by exploiting the prototype pollution vulnerabilities.
Here, the toString is an in-built function of every javascript prototype object. The attack payload overwrites the toString function with a different value. Because of this, the code breaks in the places where the target object makes use of this function in the application. This form of exploitation originating from the client results in Denial of service.
Following snippet is a proof of concept for RCE based prototype pollution vulnerability.
When the payload is parsed using JSON.parse()
, the properties of the parsedObj
are assigned to the obj
using Object.assign().
By accessing obj.polluted
, we can check if the prototype pollution was successful. In this case, if obj.polluted
evaluates to true
, it triggers the execution of the arbitrary code within the if
block, which logs the message "Remote code execution achieved!" to the console.
Real-time example:
progressbar.js is an NPM package which is responsive and slick progress bar with animated SVG paths. This package is recently found to be affected with Prototype pollution as reported in Snyk.
The code snippet from progressbar.js package implements a function extend()
that merges properties from the source
object into the destination
object. It allows for deep merging of nested objects if the recursive
flag is set to true
. The function returns the modified destination
object after the merging process. This leads to prototype pollution as this function is exported to be used by other applications.
The proof of concept attack for this vulnerability is presented as follows by the researcher:
The code begins by importing the progressbar.js
library. The BAD_JSON
variable is assigned the result of parsing a JSON string. The JSON string {"proto":{"test":123}}
is parsed using JSON.parse()
, converting it into a JavaScript object. However, this object has a prototype pollution vulnerability due to the presence of the proto
property.
The code then attempts to extend an empty JavaScript object ({}
) with the properties from the BAD_JSON
object using the progressbar.utils.extend()
function. This function, likely provided by the progressbar.js
library, merges the properties from the BAD_JSON
object into the empty object. However, since the BAD_JSON
object has a prototype pollution vulnerability, it can modify the prototype of the destination object, potentially causing unexpected changes. The code logs the value of {}.test
before and after the extension to observe the impact of prototype pollution.
There are lots of such prototype pollution vulnerability reports and STATs to discover them in the NPM packages automatically. Every identified prototype pollution in the source code is a potential vulnerability and it is confirmed for exploitability only when a successful proof of concept is provided by the security researcher.
Now for the developers to take notes, practice the following preventive measures in the code-level to avoid getting critical CVEs for your packages:
In conclusion, prototype pollution is a serious security vulnerability that can lead to unexpected and harmful consequences in JavaScript applications. It allows unauthorized modifications to object prototypes, potentially enabling attackers to execute arbitrary code or manipulate application behavior.
Feel free to leave your comments if you feel to add more stuff to this :)
Check out my other stories — SuprajaBaskaran — Medium