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:

  1. 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).

  2. 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.

  3. Craft Malicious Payload: In Repeater, identify parameters that are used to dynamically build or modify objects. Common targets are JSON payloads in POST requests 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 adding someProperty to the Object.prototype.
    • Targeting Nested Objects: Prototype pollution often works by targeting nested object assignments. If an application merges objects using a function like Object.assign or 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 userInput object into another object, Object.prototype.isAdmin will be set to true.

  4. 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 to true even 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.

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.

  1. Sanitize Input: Before using any user-provided data to construct or modify objects, validate and sanitize it. Remove or disallow keys like __proto__, constructor, and prototype.
  2. 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.
  3. Avoid for...in Loops on Untrusted Data: If you must iterate over object properties, use Object.keys() or Object.getOwnPropertyNames() which only iterate over own properties and don’t traverse the prototype chain.
  4. Use Libraries Wisely: Be aware of how libraries handle object merging and cloning. Prefer libraries that offer built-in protection against prototype pollution.
  5. Freeze Prototypes: In some environments, you can prevent modification of Object.prototype by 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.

Want structured learning?

Take the full Burpsuite course →