Prototype pollution is a type of vulnerability that affects JavaScript applications, often found in Node.js (npm) packages. This vulnerability allows an attacker to inject properties into an object’s prototype, potentially leading to unexpected behavior and security issues. Here’s how it works and how it can be exploited:

How Prototype Pollution Works

JavaScript Prototypes:

In JavaScript, objects can inherit properties and methods from their prototype. Every JavaScript object has a prototype, and objects can be created with the Object.create method, inheriting the properties from a prototype object.

Manipulating the Prototype Chain:

Prototype pollution occurs when an attacker can modify the prototype of built-in objects like Object, Array, etc. By injecting properties into the prototype, the attacker can affect all objects that inherit from that prototype.

Vulnerable Code Patterns:

Prototype pollution typically happens due to improper handling of user input when merging objects, assigning properties, or copying objects. Commonly used utility functions like merge, extend, or similar can be vulnerable if they do not validate input properly.

Example of Prototype Pollution

Consider the following code that merges two objects:

const merge = (target, source) => {
  for (let key in source) {
    if (source.hasOwnProperty(key)) {
      target[key] = source[key];
    }
  }
  return target;
};

let a = {};
let b = JSON.parse('{"__proto__":{"polluted":"yes"}}');

merge(a, b);
console.log({}.polluted); // Outputs: "yes"

In above example:

  • The merge function copies properties from the source object b to the target object a.
  • The input JSON for b includes a __proto__ property, which is a reference to the prototype of all objects.
  • When merge function processes b, it adds the polluted property to the prototype of all objects.
  • As a result, all objects now have a polluted property with the value “yes”.

When she reached the first hills of the Italic Mountains, she had a last view back on the skyline of her hometown Bookmarksgrove, the headline of Alphabet Village and the subline of her own road, the Line Lane.

Mitigating Prototype Pollution

To prevent prototype pollution, you can take the following measures:

Validate Input:

Ensure that user-supplied data does not contain keys like __proto__, constructor, and prototype.

Use Safe Methods:

Use libraries and methods that handle object merging safely. Many modern libraries like lodash have addressed this issue in their implementations.

Avoid Prototype Manipulation:

Avoid using code that directly manipulates prototypes unless absolutely necessary and understand the security implications.

Security Testing:

Regularly perform security testing, including static analysis and dynamic analysis, to identify and remediate vulnerabilities in your codebase.

Example of a Safe Merge Function

Here’s an example of a safer merge function that avoids prototype pollution:

const safeMerge = (target, source) => {
  for (let key in source) {
    if (source.hasOwnProperty(key) && key !== '__proto__') {
      target[key] = source[key];
    }
  }
  return target;
};

let a = {};
let b = JSON.parse('{"__proto__":{"polluted":"yes"}}');

safeMerge(a, b);
console.log({}.polluted); // Outputs: undefined

In this modified function:

The if condition checks that the key is not __proto__ before copying it to the target object, thus preventing prototype pollution.

By understanding and applying these principles, you can protect your applications from prototype pollution vulnerabilities.

we can extend the vulnerable example to include an XSS payload to demonstrate how prototype pollution can be used to inject malicious code. Here, I’ll modify the vulnerable example to include an XSS payload and create a simple proof of concept.

Step 1: Modify the Vulnerable Example to Include XSS

Modify vulnerable/index.js:

// vulnerable/index.js
const express = require('express');
const app = express();

const merge = (target, source) => {
  for (let key in source) {
    if (source.hasOwnProperty(key)) {
      target[key] = source[key];
    }
  }
  return target;
};

// Vulnerable route
app.get('/merge', (req, res) => {
  let target = {};
  let source = JSON.parse(req.query.source);
  merge(target, source);

  // Injecting the polluted property directly into the HTML response
  res.send(`<div>${target.xss || ''}</div>`);
});

// Start the server
app.listen(3000, () => {
  console.log('Server running on http://localhost:3000');
});

Update vulnerable/package.json to include express:

// vulnerable/package.json
{
  "name": "vulnerable",
  "version": "1.0.0",
  "description": "A vulnerable example of prototype pollution",
  "main": "index.js",
  "scripts": {
    "start": "node index.js"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "express": "^4.18.2"
  }
}

Step 3: Proof of Concept for Prototype Pollution Leading to XSS

Open a web browser and navigate to:

http://localhost:3000/merge?source={"__proto__":{"xss":"<script>alert('XSS')</script>"}}

Navigate to:

http://localhost:3000/merge?source={"x":"y"}

The injected XSS payload (<script>alert('XSS')</script>) will be included in the response due to prototype pollution, demonstrating the vulnerability.

Explanation

  • The vulnerable merge function allows the prototype of objects to be polluted with arbitrary properties.
  • By passing {"__proto__":{"xss":"<script>alert('XSS')</script>"}} as a query parameter, we can inject the xss property into the prototype of all objects.
  • When accessing any object property that doesn’t exist directly on the object, it will check the prototype chain and find the xss property.
  • The second request to /merge?source={"x":"y"} then includes the polluted prototype, which results in the <script>alert('XSS')</script> being executed.

Categorized in:

Tagged in:

,