Skip to content

Feature proposal: Mitigation for Client-Side Prototype Pollution #33

@shhnjk

Description

@shhnjk

Client-Side Prototype Pollution (will refer as PP) are increasing. For example, this shows many libraries are vulnerable to PP just by parsing location.search.

But essentially, a PP bug can be introduced if a JS application has a custom function to merge/clone objects, where an object is controlled by an attacker.

For example, following is a vulnerable code:

function isObject(obj) {
  return obj !== null && typeof obj === 'object';
}

function merge(a, b) {
  for (let key in b) {
    if (isObject(a[key]) && isObject(b[key])) {
      merge(a[key], b[key]);
    } else {
      a[key] = b[key];
    }
  }
  return a;
}

const internalObj = {a: 1, b: 2};
const userSuppliedObj = JSON.parse('{"__proto__":{"polluted": 1}}');
merge(internalObj, userSuppliedObj);
const newObj = {};
console.log(newObj.polluted); // 1

Proposal

Create a Document Policy to stop PP.

Name: restrict-prototype (bikeshed).
Keywords: globalObject, allObjects, and (if possible) objectsIfImplicit (all bikeshed).

Note: I'm still unsure if Arrays should be protected. But if so, we can add same keywords for Arrays.

globalObject

This allows developers to set prototype of local objects, but not for the global Object.

For example:

let obj = {};
obj.__proto__.test = 1;
obj.test; // 1
Object.test; // undefined

This way, prototype addition/modification only affects a local object but not all other objects.

allObjects

This ignores prototype modification of all objects. Which is equivalent of Object.freeze(Object.prototype);

For example:

let obj = {};
obj.__proto__.test = 1;
obj.test; // undefined
Object.test; // undefined

objectsIfImplicit

This allows prototype modification only when explicitly specified.
Explicit prototype modification is defined by property set with the dot notation property accessor. Setting prototype other than the dot notation will be ignored (such as bracket notation).

For example:

let obj = {};
obj["__proto__"]["test"] = 1;
obj.test; // undefined
Object.test; // undefined

obj.__proto__.test = 1;
obj.test; // 1
Object.test; // 1

I'm not sure how technically feasible this option is (in terms of browser implementation), but it seems possible at least in Chromium.

This option is beneficial because most (if not all) of PP bugs are introduced with the code which accesses prototype with bracket notation (obviously this is unintentional). This option looks like a best way to eliminate PP while allowing developers to intentionally modify object prototypes.

Special thanks to @koto for some ideas.
CC: @mikewest and @arturjanc

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions