js-prototype-pollution
JS Prototype Pollution
Prototype Pollution is a class of vulnerability in JavaScript applications that allows an attacker to modify the prototype of base objects such as Object. Because JavaScript uses prototype-based inheritance, every object inherits properties and methods from its prototype. If an attacker can inject or modify properties in this prototype, those malicious attributes will be inherited by all objects in the application.
This leads to unexpected behavior and, in some cases, can result in:
- Authentication bypass
- Authorization bypass
- Remote Code Execution (RCE)
- Data leakage
- Denial of Service
- Logic manipulation
A famous real-world example was found in libraries like Lodash, where unsafe object merging allowed attackers to inject properties into the global object prototype. Reference: here
In this exercise, the application runs Node.js in CGI mode to reduce the impact, but in real-world environments, the pollution persists and affects all users.
Understanding JavaScript Prototypes
JavaScript objects inherit from other objects through prototypes. The __proto__ property allows access to an object's prototype.
Example:
let a = {};
let b = {};
b.__proto__.isAdmin = true;
console.log(a.isAdmin); // true
Here, even though a never had the property, it inherits it from the polluted prototype.
This is the core idea behind prototype pollution.
Root Cause
The main root cause is unsafe object merging or property assignment, where user input is directly merged into objects without filtering dangerous properties.
Common vulnerable patterns:
- Deep merge functions
- Object.assign() with user input
- Recursive merge utilities
- Libraries without proper validation
- JSON parsing with unsafe keys
The dangerous keys include:
__proto__constructor.prototypeprototype
If these keys are not filtered, attackers can modify the global prototype.
The Vulnerable Merge Function
Many applications use merge functions to combine objects:
Example:
function merge(target, source) {
for (let key in source) {
target[key] = source[key];
}
}
If the user sends:
{
"__proto__": {
"isAdmin": true
}
}
The function will modify the prototype instead of just the object.
This results in:
{}.isAdmin === true
This affects every object in the application.
Exploitation Steps
-
Source Code Review: The first step is to find where user-controlled input is processed. Common places:
- API endpoints accepting JSON
- User profile updates
- Configuration upload
- Query parameters
- Deep object merging
Look for:
- Object merge functions
- JSON parsing without validation
- Libraries known to be vulnerable
-
Identify the Injection Point: Find where user input is merged into server objects.
Example:
app.post("/api", (req, res) => {
merge(config, req.body);
});
- Craft the Payload: Use
__proto__to inject malicious properties.
Example:
{
"__proto__": {
"isAdmin": true
}
}
Other advanced payloads:
{
"constructor": {
"prototype": {
"isAdmin": true
}
}
}
- Trigger the Vulnerable Logic: After pollution, trigger any functionality that uses the polluted property.
Example:
- Login system
- Authorization check
- Configuration loader
- Template engine
-
Escalate Impact: In real-world scenarios, attackers can escalate the attack to:
- RCE via template injection
- SSRF
- XSS
- Bypass sandbox
- Modify environment variables
Example:
{
"__proto__": {
"shell": "malicious"
}
}
Detection Techniques in Bug Bounty
When hunting:
- Look for JSON APIs
- Fuzz with
__proto__ - Check unusual behavior
- Look for reflected or persistent changes
- Test admin logic bypass
- Test debug flags
Useful payloads:
{"__proto__":{"test":"polluted"}}
Then check:
{}.test