Prototype pollution

In the realm of web security, there's a potent threat known as prototype pollution, which poses significant risks for web applications. At its core, prototype pollution leverages a quirk in JavaScript. To grasp its implications, consider this: JavaScript enables the alteration of all attributes associated with an object. This malleability allows not only legitimate development but also opens a door for potential exploitation.

The crux of the issue lies in the ability of an attacker to manipulate attributes of objects that, under normal circumstances, they wouldn't have access to. This manipulation can lead to cascading breaches that compromise application security. In essence, prototype pollution grants a malicious actor the power to inject values that overwrite or contaminate a JavaScript application's objects and properties, its object prototypes.

Understanding Prototypes and Inheritance

When an attempt is made to access a property of an object, the search for that property goes beyond the object itself. It extends to the prototype of the object, then to the prototype of that prototype, and so forth. This process continues until either a property with a matching name is discovered, or the journey along the prototype chain reaches its conclusion. This mechanism, known as "prototype chaining," is a foundational aspect of JavaScript's inheritance model and plays a crucial role in shaping how objects inherit and share properties and behaviors.

const person = {
    firstName: 'Thomas',
    lastName: 'Edison',
    __proto__: {
        birth: '11,02,1847',
        city: 'Milan',
    },
};
const person = {
    firstName: 'Thomas',
    lastName: 'Edison',
};
person.__proto__.birth = '11,02,1847';
person.__proto__.city = 'Milan';

In JavaScript, functions can be added to objects as properties. This applies even to inherited functions, which function much like any other property. This includes scenarios where a property in an object hides a property in its prototype.

const person = {
    firstName: 'Thomas',
    lastName: 'Edison',
    fullName() {
        return `${this.firstName} ${this.lastName}`
    },
};

const scientist = {
    __proto__: person,
};

I believe this is another topic that requires a more comprehensive explanation, but I hope the provided information offers a foundational understanding.

Prototype Pollution

Objects inherit properties from prototypes. This means that once you prototype an object with a property, that property will be present in all other objects declared either after or before. This allows you to manipulate JS objects in runtime.

let guest1 = { role: 'guest'};
Object.prototype.role= 'admin';
let guest2= { };

console.log(guest1 .role); // guest
console.log(guest2.role);  // admin

However, using JSON structures may also be vulnerable to attacks. In this example, the "__proto__" key is used to directly manipulate the prototype of the object.

const json = '{ "__proto__": { "role": "admin" } }';
const guest = JSON.parse(json);

Pollution is only one piece of the security issue puzzle, and it often intertwines with other vulnerabilities in a security breach.

Prevention

Multiple strategies can be employed to prevent prototype pollution. One approach involves crafting a custom JSON parser, as demonstrated in the example below, or implementing similar code to sanitize objects.

var parse = function (input) {
    const keys = ['firstName', 'lastName', 'birth', 'city'];
    try {
        return JSON.parse(input, (key, value) => {
            if (!keys.includes(key)) {
                return undefined;
            }
            return value;
        });
    } catch (error) {
        return null;
    }
}

const json= '{"__proto__": {"role": "admin"}, "firstName": "Thomas", "lastName": "Edison"}';
const user = parse(json);
console.log(user);

Another method to consider is using a custom sanitization function, such as shown below:


var sanitize = function (data) {
    const keys = ['firstName', 'lastName', 'birth', 'city'];
    const output = {};

    for (const key in data) {
        if (keys.includes(key)) {
            output[key] = data[key];
        }
    }
    return output;
}

const input = { "__proto__": { "role": "admin" }, "firstName": "Thomas", "lastName": "Edison" };
const user = sanitize(input);
console.log(user);

To bolster your defenses, always utilize secure and up-to-date libraries, and steer clear of any recursive merges. Utilizing Object.freeze() can help, although it's not foolproof, as it prevents object property updates. Another effective strategy is to use Object.create(null) — this creates a new object without prototype properties, providing an additional layer of protection against prototype pollution.