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 -> nullReading 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.prototypeis a property on a constructor function, the object its instances inherit from.constructorpoints 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.