Skip to main content

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

danger

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.prototype
  • prototype

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

  1. 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
  2. Identify the Injection Point: Find where user input is merged into server objects.

Example:

app.post("/api", (req, res) => {
merge(config, req.body);
});
  1. Craft the Payload: Use __proto__ to inject malicious properties.

Example:

{
"__proto__": {
"isAdmin": true
}
}

Other advanced payloads:

{
"constructor": {
"prototype": {
"isAdmin": true
}
}
}
  1. Trigger the Vulnerable Logic: After pollution, trigger any functionality that uses the polluted property.

Example:

  • Login system
  • Authorization check
  • Configuration loader
  • Template engine
  1. 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