Link Search Menu Expand Document

Prototype Pollution

  1. Prototype Pollution
    1. Description
    2. Impact
    3. Scenario 1
    4. Scenario 2
    5. Prevention
    6. Testing
    7. References

Description

The problem lies with the manner in which JavaScript implements inheritance by using a prototype. What this means, in a nutshell, is that every object contains a reference to the prototype of its class. When a property is requested from a particular object, the runtime first checks if the instance has the aforementioned property; otherwise, it looks it up in the prototype chain, recursively.

The reference to the prototype is available via the __proto__ property. The fact that the prototype itself is just an object that has properties makes the whole structure susceptible to unwanted alteration when properties have been assigned from any untrusted input.

Impact

The impact of Prototype Pollution is ultimately determined by the sensitivity and criticality of the data ingested by the application. It is not a vulnerability that is dangerous per se; rather, it all depends on how the application uses such untrusted properties. In other words, it merely alters the program data and flow.

Scenario 1

The following example considers an object (in a JavaScript console):

> o = {x: 123, __proto__: {toString: () => console.log('Triggered!')}}
{ x: 123 }
> o + ''
Triggered!
'undefined'

We have created an object with its own prototype that redefines the toString property.

While this is important, it is unlikely that some kind of injection (e.g., an injection via GET parameters, cookies, etc.) ends up creating a JavaScript function (() => console.log('Triggered!') in the above example unless the application uses eval or some other mechanism. Instead, it is likely that an attacker is able to place strings, numbers, an array, or objects.

In this case, there is no direct code execution, and the impact is merely that of adding another property to the object; since we are already injecting __proto__ itself, this is not particularly interesting. It could still be possible to introduce a denial of service by overwriting some methods with non-function values, for example {toString: 123}.

Scenario 2

In this case, however, the application allows the alteration of an existing prototype. Consider the following:

const someObject = {};
const someOtherObject = {};

// ...

someObject[UNTRUSTED_NAME] = UNTRUSTED_VALUE;

// ...

if (someOtherObject.isAdminEnabled) {
    // do some sensitive stuff
}

Being able to control UNTRUSTED_NAME and UNTRUSTED_VALUE can be used to alter the prototype of all the other objects (not only someObject) with the ultimate effect of setting someOtherObject.isAdminEnabled to true. Specifically, using these values:

UNTRUSTED_NAME = '__proto__';
UNTRUSTED_VALUE = {isAdminEnabled: 'whatever'};

Prevention

There are different approaches to preventing Prototype Pollution, the most trivial of which is to disallow untrusted data being assigned to arbitrary properties altogether. If this is not possible, developers might implement some filtering so that it is not possible to overwrite __proto__ or other special properties of an object.

Testing

Verify that the application protects against JavaScript injection attacks, including for eval attacks, remote JavaScript includes, DOM XSS, and JavaScript expression evaluation.

References

The Daily Swig - Prototype pollution: The dangerous and underrated vulnerability impacting JavaScript applications

Medium - What is Prototype Pollution and why is it such a big deal?