avatarParthipan Natkunam

Summarize

Unraveling JavaScript Prototype Pollution: Understanding and Prevention

A Hands-on Guide on Exploitation & Prevention

JS Prototype Pollution — A Quick Exploit Recipe

JavaScript is based on prototypal inheritance. We’ll briefly look at what it means but for now, it is important to understand that this nature of the language is what makes the prototype pollution exploits possible.

What is Inheritance in Programming (Classical Inheritance)?

It is a pattern in Object Oriented Programming to share certain properties and behaviors that are common among the entities in your code.

Let’s assume that you are implementing a program that calculates the area of 2D geometric shapes. Then there are some properties that all shapes will have such as area and perimeter.

so we could model the shape entities as follows:

Inheritance

In a classical object-oriented language like Java, we would express the above relation as follows:

// Base class Shape
class Shape {
    String type = "shape";

    public void draw() {
        System.out.println("Drawing " + this.type);
    }
}

// Square class inheriting from Shape
class Square extends Shape {
    Square() {
        this.type = "square";
    }
}

// Circle class inheriting from Shape
class Circle extends Shape {
    Circle() {
        this.type = "circle";
    }
}

public class Main {
    public static void main(String[] args) {
        Square square = new Square();
        square.draw(); // Output: Drawing square

        Circle circle = new Circle();
        circle.draw(); // Output: Drawing circle
    }
}

What we see above is a classical inheritance. But in Javascript inheritance works a bit differently. There is even a term for it named “Prototypal inheritance”. Let’s look at what that is in the next section because understanding this concept is crucial to knowing why and how prototype pollution works.

Prototypal Inheritance

Prototypal inheritance in JavaScript is a system where objects inherit properties and methods from a prototype. This prototype is an internal link to another object and acts as a template for the object's inheriting properties. When a property is accessed, JavaScript looks up the property chain until it finds a match or reaches the end of the chain.

Objects in JavaScript will have an internal property named __proto__ which maintains the parent object’s prototype reference.

So when we access a property or method in an object, JS will traverse up this __proto__link until it either finds it in the chain or reaches null which is the terminal node in the JS prototype chain.

JS Prototypal Inheritance Hierarchy
function Shape() {
    this.type = 'shape';
    this.draw = function() {
        console.log('Drawing ' + this.type);
    }
}

function Square() {
    this.type = 'square';
}

function Circle() {
    this.type = 'circle';
}

Square.prototype = new Shape();
Circle.prototype = new Shape();

let square = new Square();
square.draw(); // Output: Drawing square

let circle = new Circle();
circle.draw(); // Output: Drawing circle

You can get a deeper insight into prototypes and constructor functions in JS, from my past article on the topic here:

Prototype Pollution

Now that we have seen how prototypal inheritance works in JS, let’s look further into a security vulnerability that arises from this behavior named “Prototype pollution”.

This vulnerability occurs when an attacker manipulates the prototype of the default object prototype, enabling them to inject properties and alter the behavior of all instances inheriting from that prototype.

Privilege Escalation Through Prototype Pollution

As we have seen in the earlier section, in JavaScript, every object inherits properties and methods from the default Object prototype. If the prototype is altered, all objects inheriting from that prototype are affected.

let payload = JSON.parse('{"__proto__": {"isAdmin": true}}');
Object.assign({}, payload);
console.log({}.isAdmin); // Outputs true

This example illustrates how Prototype Pollution can lead to privilege escalation and other serious security breaches.

A Service Take-down Scenario

In JS, the Object prototype contains a toString method. If an API uses this method on a custom object and is vulnerable to prototype pollution, an attacker could pollute the prototype and make the server application throw, thereby bringing down the entire API.

sample payload:

curl -H "Content-Type: application/json" -X POST 
-d '{"settings": {"__proto__": 
{"toString": "I am overriding you, muhahaha..."}}}' 
https://api.your-app.com/users/42

If proper mitigation doesn’t exist on the server, the above payload will pollute the toString() method on the default object prototype.

So, in the server’s business logic, if there’s toString invocation anywhere, the above payload will induce an HTTP status 500 error and bring down the entire API.

Preventing Prototype Pollution

The following approaches can be used to prevent our APIs from being exploited through prototype pollution:

Sanitize Input

As with the prevention of most other attacks, never trust user input and always sanitize it before processing or storage:

function sanitizeInput(input) {
    if (input.hasOwnProperty('__proto__')) {
        delete input['__proto__'];
    }
    // Additional sanitization logic...
    return input;
}

Object Freezing

By using Object.freeze(), you can make an object immutable, thus preventing any modifications to its prototype. This is particularly useful for objects that should not be altered during the application's runtime.

const secureObj = Object.freeze({});

You can find a detailed article that I wrote on freezing objects here:

Leveraging Map and Set Instead of Plain Objects

For storing user-controlled data, consider using Map or Setinstead of plain objects. Map and Setdoes not suffer from Prototype Pollution as it does not link keys to a prototype.

let userMap = new Map();
userMap.set('username', 'Alice');

But for any reason, you have to depend only on object literals, then consider creating them using null:

let User = Object.create(null);

Validate Incoming JSON Data

This is similar to sanitizing user inputs and can be combined within the sanitization logic depending on the scenario.

When parsing JSON data, especially from untrusted sources, validate the data to ensure that it does not contain prototype properties. This can be achieved by using a reviver function in JSON.parse().

JSON.parse(jsonString, (key, value) => {
    if (key === '__proto__') {
        throw new Error('Prototype properties not allowed');
    }
    return value;
});

Avoid Using Recursive Merge Functions

Recursive merging of user-supplied data into an existing object can lead to Prototype Pollution. If such functionality is needed, ensure that the merge function is robust against Prototype Pollution.

Opt for libraries that are known to safely handle object merging and cloning, for instance,lodash with its _.cloneDeep() method, which is designed to avoid Prototype Pollution.

Consider Using the Node.js configuration flag

Using the --disable-proto=delete flag in Node.js will remove the __proto__ property completely. However, it is important to note that prototype pollution is still possible by using the constructor.prototype property instead.

Conclusion

Thus we have looked in-depth at what prototypal inheritance is, and how this mechanism leads to prototype pollution attacks.

We have also looked at two major possible exploits through this vulnerability, namely privilege escalation and service take-down.

Finally, we have looked at various approaches that can be combined to mitigate and prevent such exploits.

In Plain English 🚀

Thank you for being a part of the In Plain English community! Before you go:

Programming
Technology
Web Development
JavaScript
Cybersecurity
Recommended from ReadMedium