JavaScript Prototype Pollution Attack

JavaScript Prototype Pollution Attack

·

3 min read

JavaScript prototype pollution attach is a kind of attack which makes use of the prototype chain and pollute the JavaScript object. There is repo Prototype pollution attack has a detailed description for it. This article is mainly a note for this kind of attack.

We know that in JavaScript, the objects we normally used are under the prototype chain from null to Object. You can see a detailed exploration from my previous article Exploring the JavaScript Prototype Chain. The key is that each object has a property __proto__ which can be used to refer upward the prototype chain. This opens the door for changing the prototype by normal objects.

For example, if we have a number variable, we can always use the toString method to convert the number to a string.

const v1 = 100;
v1.toString();

This toString method is actually defined in Number.prototype. If we have another number variable, and somehow the attacker have the ability to use this new variable like below.

const v2 = 0;
v2.__proto__.toString = 0;

As you can see, the attacker use this __proto__ to get the Number.prototype object, and then change the toString method to be a number variable. Now if we try to use the toString as a method again, we should get an error.

v1.toString();
// v1.toString is not a function

This is how this pollution works.

Now you may ask, how this attack could be achieved? Well, the typical example is like this. We want to write a function merge to merge from source object to target object like below.

function merge(target, source) {

}

Inside this function, we will try to get all the keys from the source object, and try to copy its values to the target object. So if the source object is like this {"__proto__": {"toString": 0}}, then this function will try to write this toString property to the target object's __proto__ property. And we know target.__proto__ refers to Object.prototype, so the toString method in Object.prototype will be polluted.

A lot of third-party packages are actually have this kind of vulnerability, including lodash.merge. Lodash fixed this issue in version 4.17.5, this let's try to install the previous version 4.17.4 and try to reproduce this problem.

The process is like this. Say we are building a server, which accepts a json string from client. The server will try to parse this string into object and then use lodash.merge api to merge the object with another object.

const _ = require("lodash");  

const str = `{ "__proto__": { "toString": "123", "valueOf": "It works !", "xxx": 2 } }`;

const o = _.merge({},JSON.parse(str));

console.log(o.toString); // 123

const otherObj = {};
console.log(otherObj.toString) // 123

As you can see, the Object.prototype is polluted.

Due to this pollution, something more serious may happens. In JavaScript, new properties in the prototype are enumerable. This means it can be accessed by the for-in loop. So this means that if the attacker pollute the Object.prototype with new properties, then all the other objects using for-in loop are in danger. Let see an example.

const _ = require("lodash");

const otherObj = { "x": 1, "y": 2 };

// before pollution
for (const key in otherObj) {
    console.log(key);
}
// x y

// newly added property z
const str = `{ "__proto__": {  "z": 3 } }`;
_.merge({}, JSON.parse(str));

// after pollution
for (const key in otherObj) {
    console.log(key);
}
// x y z

A few measures can be used to prevent this kind of attack in the repo. First, freeze the prototype like Object.freeze(Object.prototype). Second, use a json schema validation package to check the input more rigorously like ajv. Third, use map to replace object. Fourth, use Object.create(null) to use an object which doesn't has prototype.