Skip to content
العربية

Prototype Pollution

How a __proto__ key in merged JSON writes onto the shared prototype so every object inherits the attacker's values — and why key blacklists keep failing.

Estimated time
25 min
Level
Intermediate
Your progress in this track0%

In JavaScript every plain object inherits from a shared prototype. Prototype pollution is when attacker-controlled data writes a property onto that shared prototype instead of onto one object — so afterwards every object in the app appears to have that property. A single polluted isAdmin can flip authorization checks across the whole application.

In these labs the pollution is simulated against a stand-in prototype object, so it never touches the real Object.prototype or the lab engine. The behaviour you see — a fresh object inheriting the planted value — is exactly what real pollution does.

What is the vulnerability?

A recursive merge copies user JSON into an object:

merge.js
for (const key in source) {
  if (typeof source[key] === 'object') merge(target[key] ||= {}, source[key]);
  else target[key] = source[key];   // __proto__ flows straight through
}

Send {"__proto__": {"isAdmin": true}} and the merge writes isAdmin onto the prototype. Later, ({}).isAdmin is true everywhere.

Why does it happen?

The merge trusts every key. The prototype is reachable by several names — __proto__, and constructor.prototype — so banning just __proto__ leaves constructor open, and checking only the top-level keys leaves a nested __proto__ to pollute as the recursion descends.

How do you stop it?

Skip the dangerous keys — __proto__, constructor, prototype — at every depth of the merge, not just the top. Better, avoid merging into plain objects at all: use a Map, or objects created with Object.create(null) that have no prototype to pollute. Validate input against a schema so unexpected keys never reach the merge.

The three labs below walk the ladder: pollute via __proto__, bypass a __proto__ strip with constructor.prototype, and bypass a top-level check with a nested __proto__.

Labs

  • Pollution via merge

    EasyBrowser lab

    Pollute the prototype so a fresh panel becomes an admin panel.

  • Past a __proto__ filter

    MediumBrowser lab

    Pollute the prototype despite the __proto__ strip.

  • Pollution via a nested path

    HardBrowser lab

    Pollute the prototype with a nested key that bypasses the shallow check.