Prototype Pollution: The Complete Practical Guide

JavaScript Prototypes and Inheritance

The prototype model behind prototype pollution: the lookup chain, __proto__ vs prototype vs constructor, and why a shared prototype is a global write target.

Published

Prototype pollution only makes sense once you understand how JavaScript inheritance works, because the whole attack is an abuse of the prototype lookup chain. This page covers the model: prototypes, the chain, and the three names that reach the shared prototype. Practice on a free live target.

What a prototype is

Every JavaScript object has an internal link to another object called its prototype. The prototype is where the object inherits properties and methods from. When you call [].map(...), the array does not have its own map, it inherits it from Array.prototype. When you read a property an object does not define itself, the engine looks at the prototype, then that object's prototype, and so on.

The lookup chain

Property resolution walks a chain:

myObj  ->  myObj's prototype  ->  ... ->  Object.prototype  ->  null

Reading myObj.foo checks myObj for an own foo, then each object up the chain, ending at Object.prototype. If nothing in the chain has foo, the result is undefined. For a plain object {}, the chain is short: {} then Object.prototype then null.

const o = {};
o.hasOwnProperty("x");   // "hasOwnProperty" is not on o; inherited from Object.prototype
o.x;                      // undefined; no object in the chain has "x"

That final undefined is the opening. If someone writes x onto Object.prototype, o.x stops being undefined.

proto vs prototype vs constructor

Three names you will use constantly, and they are easy to confuse:

const user = {};
user.__proto__;                       // the object user inherits from = Object.prototype
user.constructor;                     // the function that built it = Object (for a plain object)
user.constructor.prototype;           // Object.prototype again, reached via constructor
Object.prototype === user.__proto__;  // true
Object.prototype === user.constructor.prototype; // true
  • __proto__ is the instance's direct accessor to its prototype.
  • prototype is a property on a constructor function, the object its instances inherit from.
  • constructor points from an instance back to the function that created it.

The key fact for pollution: user.__proto__ and user.constructor.prototype reach the same Object.prototype. Block one name and the other still gets there.

Why the prototype is a shared global write target

Object.prototype is a single object shared by nearly every object in the program. Writing one property onto it changes what every object inherits:

Object.prototype.polluted = "yes";
({}).polluted;           // "yes"
([]).polluted;           // "yes"
(function(){}).polluted; // "yes"

One write, global effect. That is why prototype pollution is so powerful: the attacker is not editing one object, they are editing the default every object falls back to. Everything after this spoke is about how an attacker reaches that write from untrusted input, and what code later reads it.

Where to go next

Now see how an attacker performs that write from the browser in client-side prototype pollution, then learn to find the sources and gadgets in finding sources and gadgets. Or jump straight to a free live target.

← Back to Prototype Pollution: The Complete Practical Guide