Prototype pollution is a vulnerability that allows an attacker to inject properties into an object’s prototype, which can then affect all objects that inherit from that prototype.
Let’s see this in action. Imagine a simple JavaScript application that takes user input and uses it to dynamically add properties to an object.
// Server-side code (Node.js example)
const express = require('express');
const app = express();
app.use(express.json());
app.post('/update-user', (req, res) => {
const userId = req.body.userId;
const newProperty = req.body.newProperty;
const propertyValue = req.body.propertyValue;
// This is where the vulnerability lies:
// Directly assigning properties from user input without sanitization.
const user = { id: userId };
user[newProperty] = propertyValue;
// In a real app, this might update a database or cache.
console.log(`User ${userId} updated with ${newProperty}: ${propertyValue}`);
res.send('User updated');
});
app.listen(3000, () => console.log('Server listening on port 3000'));
If an attacker sends a request like this:
{
"userId": "123",
"newProperty": "__proto__.isAdmin",
"propertyValue": "true"
}
The user object becomes:
{
id: "123",
__proto__: { isAdmin: "true" }
}
Now, any object created later that inherits from Object.prototype will have an isAdmin property set to true. This could be used to bypass authorization checks, execute arbitrary code, or cause denial-of-service conditions.
The Core Problem: Unchecked Input
The fundamental issue is trusting user-controlled input to directly manipulate object structures. In JavaScript, objects inherit properties from their prototypes. When you modify Object.prototype, you’re effectively modifying the blueprint for all plain JavaScript objects.
How Burp Suite Helps
Burp Suite is invaluable for finding prototype pollution because it allows you to intercept and manipulate HTTP requests. You can then send these modified requests to your target application and observe the results.
Here’s a typical workflow:
-
Proxy Traffic: Configure your browser to use Burp Suite as a proxy. Browse the target application to capture relevant requests, especially those that handle user input (e.g., form submissions, API calls).
-
Send to Repeater: Right-click on a request in the Proxy History tab and select "Send to Repeater." Repeater allows you to modify and resend requests multiple times.
-
Craft Malicious Payload: In Repeater, identify parameters that are used to dynamically build or modify objects. Common targets are JSON payloads in
POSTrequests or URL parameters.- The
__proto__Key: The magic string here is__proto__. This is a special property in JavaScript that references an object’s prototype. By setting__proto__.someProperty = someValue, you’re addingsomePropertyto theObject.prototype. - Targeting Nested Objects: Prototype pollution often works by targeting nested object assignments. If an application merges objects using a function like
Object.assignor a custom deep merge, and one of the source objects contains__proto__, the pollution can occur.
Consider this scenario:
// A vulnerable merge function function mergeObjects(target, source) { for (const key in source) { if (source.hasOwnProperty(key)) { target[key] = source[key]; } } return target; } // Application code receiving user input let config = { theme: 'light' }; let userInput = JSON.parse(req.body.config); // User provides: { "__proto__": { theme: 'dark' }, theme: 'light' } mergeObjects(config, userInput); // Now config.theme is 'light', but Object.prototype.theme is 'dark'The attacker would craft a payload like:
{ "__proto__": { "isAdmin": "true", "defaultUserRole": "admin" }, "username": "attacker" }If the application merges this
userInputobject into another object,Object.prototype.isAdminwill be set totrue. - The
-
Test for Impact: After sending the malicious request, you need to verify if the pollution was successful.
- Check Global State: The most direct way is to try and access the injected property on a different, unrelated object. If you can send a request that causes the application to render a page or perform an action that checks for
isAdmin(e.g.,if (user.isAdmin) { ... }), and it evaluates totrueeven for a non-admin user, you’ve likely succeeded. - Server-Side Logs: Look for any unexpected output in the server logs.
- Error Messages: Sometimes, prototype pollution can lead to errors if subsequent code expects a certain property to not exist on the prototype.
- Check Global State: The most direct way is to try and access the injected property on a different, unrelated object. If you can send a request that causes the application to render a page or perform an action that checks for
Common Vulnerable Patterns
- JSON Parsing: Applications that parse JSON payloads from user input and directly assign properties.
- Object Merging/Cloning: Libraries or custom functions that merge or clone objects without sanitizing keys like
__proto__. - Configuration Loading: Systems that load configuration objects from external sources, including user-controlled ones.
- Templating Engines: Some templating engines can be tricked into executing code or accessing global objects if they don’t properly escape or sanitize interpolated values.
Mitigation
The most robust defense is to never trust user input.
- Sanitize Input: Before using any user-provided data to construct or modify objects, validate and sanitize it. Remove or disallow keys like
__proto__,constructor, andprototype. - Use Safe Object Creation: Instead of directly assigning properties, use safer methods. For example, when dealing with JSON, use
JSON.parse(input, (key, value) => { ... })which provides a reviver function to inspect and transform values during parsing. - Avoid
for...inLoops on Untrusted Data: If you must iterate over object properties, useObject.keys()orObject.getOwnPropertyNames()which only iterate over own properties and don’t traverse the prototype chain. - Use Libraries Wisely: Be aware of how libraries handle object merging and cloning. Prefer libraries that offer built-in protection against prototype pollution.
- Freeze Prototypes: In some environments, you can prevent modification of
Object.prototypeby freezing it:Object.freeze(Object.prototype);. However, this might break legitimate application functionality if it relies on prototype manipulation.
The next challenge you’ll encounter is understanding how to exploit prototype pollution to achieve arbitrary code execution, which often involves chaining it with other vulnerabilities or leveraging specific application logic.