Introduction
JavaScript is fundamentally a prototype-based language. This means that objects do not inherit features from classes (like in class-based languages, e.g., Java), but rather through a mechanism called prototypes. Prototypes are the primary mechanism by which JavaScript objects inherit features from one another.
This characteristic has a critical security implication: if the prototype object is modified in one object, that modification will apply to every other object in the application that inherits from it.
In the example, we have two variables, a and b. We modify the prototype object attached to variable a by adding a property called foo and giving it the value bar. You might assume this would have no effect on variable b, but it does. The modified prototype object is inherited by variable b. Consequently, when we call the foo property on b, it successfully prints bar.
Understanding Prototype Pollution
As stated, JavaScript is a prototype-based language, meaning that any modification to the prototype object will persist across all objects that inherit from it. Prototype Pollution occurs when an attacker can inject properties into the base prototype object (often referenced as Object.prototype).
Let’s look at a common scenario in an application that processes user input:
The goal here for an attacker is to set the admin variable to true.
The code is attempting to merge user-supplied data (data) with an existing user object (user). After the merge, the application checks if admin.admin is set to true. Under normal circumstances, an attacker would never be given the chance to directly modify the private admin variable in this context, making the check seem secure.
However, during the merge process, if the application’s recursive merging function encounters a prototype object property, it may inadvertently add that property to the user object’s prototype chain. Since the prototype object is inherited by all other objects, we can potentially modify other objects or variables—including the admin object—as shown in the exploit request below.
Exploitation via Input Merging
An attacker can exploit this by sending a specifically crafted payload within the user data:
In the example curl request, the attacker is sending a payload that contains the prototype object (__proto__ or similar keyword used in the target application’s merging utility) with a property called admin which is set to true.
When the application’s merging utility processes this input, it recursively injects the admin: true property into the global Object.prototype.
Later, when the line checks to see if admin.admin is set to true, the check will succeed because the admin object inherited the admin property (true) from the compromised prototype object. The attacker has successfully bypassed the intended security check.
Summary and Impact
Prototype Pollution can be conceptually thought of as a type of object injection. Since the base prototype object is inherited by virtually all objects, if an attacker can modify it in one place, that change will be inherited by everything else.
This vulnerability can be used to overwrite functions, variables, and any other property that is accessed without first checking for the property’s existence on the object itself.
Although this is a lesser-known vulnerability compared to XSS or SQL Injection, it is extremely dangerous. In the past, successful exploitation of Prototype Pollution has led to severe security consequences, including Cross-Site Scripting (XSS), Denial-of-Service (DoS) attacks, and even Remote Code Execution (RCE). The potential scope of this attack is vast and is limited only by the properties the attacker can overwrite and how those properties are used elsewhere in the application’s codebase.