The Key That Poisoned the Base: Prototype Pollution

Web & API Security Level 3/4 ~5 min October 26, 2026

The challenge

Thornfield's preferences endpoint takes whatever JSON you post and merges it into your settings with a small recursive helper. The helper copies every key it finds in your object, walking into nested objects as it goes. One key name is special in JavaScript, and writing through it does not change your object: it changes the thing every object inherits from. Read the code and name the key an attacker posts to set a property on every object in the process at once.

What you'll learn

  • Explain how __proto__ reaches an object's shared prototype
  • See how an unguarded recursive merge writes onto the prototype
  • Trace a defaulted property read (isAdmin) becoming true for all users
  • Name __proto__, constructor and prototype as the keys to refuse
  • State safe-merge and null-prototype defences

Skills tested

Source code reviewJavaScript object modelData-flow tracing

Prerequisites

  • Reading JavaScript
  • The idea that objects inherit from a prototype

How it works

Every plain JavaScript object inherits from a shared prototype, reachable through the key __proto__. Reading a property that an object does not define falls through to that prototype.

A recursive merge that copies every key from attacker input, and recurses into nested objects, will follow a nested __proto__ and assign onto the shared prototype instead of onto the input object. A body like {"__proto__": {"isAdmin": true}} therefore sets isAdmin on the prototype every object inherits from.

After that, any object without its own isAdmin resolves the lookup to true. An auth check that reads that defaulted property now passes for everyone.

Common mistakes

  • Answering isAdmin. That is the property that gets polluted; the key the merge fails to refuse is __proto__.
  • Answering merge. The function is the vehicle, but the question asks for the key.
  • Thinking it only changes your object. Writing through __proto__ changes the shared prototype, affecting every object.
  • Assuming a default of absent is safe. Pollution makes absent properties resolve to an attacker value.

How to defend against it

Refuse the dangerous keys and avoid defaulted security reads.

  • In any merge or deep-assign over untrusted data, skip or reject __proto__, constructor and prototype.
  • Use null-prototype objects (Object.create(null)) or a Map for data keyed by untrusted names.
  • Prefer a vetted merge library or structuredClone over a hand-rolled recursive merge; in Node consider --disable-proto.
  • Do not base a security decision on a property that is simply expected to be absent by default.

Full solution

Pro and Max members unlock the complete step-by-step walkthrough.

Go Pro

Community stats

135 completions
67% success rate
Malekith First blood

Related Daily Hacks

35,000+ Hackers Real labs Free
Start Hacking Free or solve today's hack, no account needed