JavaScript Prototype Pollution Explained
What you are about to learn
This lab takes you inside one of the most subtle and consequential vulnerability classes in the JavaScript ecosystem. You will begin from first principles — understanding how JavaScript's prototype chain works — and end by demonstrating a working exploit that grants yourself administrator access in a simulated application, all without touching the authentication logic directly. Along the way you will learn exactly why this vulnerability has been found in production code at nearly every major JavaScript library.
JavaScript prototypes: the hidden backbone of every object
When JavaScript was designed, its author chose a model of inheritance very different from the class-based model most programmers were familiar with from Java or C++. In JavaScript, every object holds a secret internal link to another object called its prototype. When you read a property from an object, JavaScript does the following in order:
- Check whether the object has an own property with that name. If yes, return it.
- If not, follow the internal prototype link to the next object and repeat.
- If the chain ends at
nullwithout finding the property, returnundefined.
This chain of links is called the prototype chain. Most chains look like this:
yourObject → Object.prototype → nullObject.prototype is the root prototype that every ordinary object in JavaScript ultimately inherits from. It provides methods you use every day — toString(), hasOwnProperty(), valueOf() — without you having to define them. Every object you create with {} or new starts this chain automatically.
Here is a concrete illustration:
const obj = {};
console.log(obj.toString()); // "[object Object]" — inherited from Object.prototype
console.log(obj.hasOwnProperty("toString")); // false — it is NOT an own propertyThe method is found on Object.prototype, not on obj itself.
The dangerous consequence: Object.prototype is shared by everyone
Because every ordinary object in a JavaScript runtime shares the same Object.prototype, any property written onto it becomes instantly visible on every object — ones that already exist and ones that have not been created yet. This is the foundation of the attack you are about to perform.
What prototype pollution is
Prototype pollution occurs when an attacker can inject a property onto Object.prototype by supplying crafted input to a function that merges objects without validating property keys. The most common scenario looks like this:
- A server-side or client-side utility function accepts a user-supplied JSON object and recursively merges it into an internal configuration or options object.
- The merge function uses a
for...inloop over the source object's keys without filtering out dangerous keys like__proto__. - The attacker supplies a payload containing the key
__proto__at the top level. - The merge function, encountering the key
__proto__, resolvestarget["__proto__"]— which is not a string lookup but the actual prototype accessor — and merges the attacker's nested object directly intoObject.prototype. - Any property in that nested object (
isAdmin,debug,role) is now present on every object in the runtime.
Why this matters in real applications
In a typical authorization check, the developer writes:
function renderDashboard(user) {
if (user.isAdmin) {
return showAdminPanel();
}
return showUserPanel();
}This code assumes user.isAdmin will only be true if it was explicitly set when the user object was created. After prototype pollution with {isAdmin: true} on Object.prototype, every call to renderDashboard — for every user — returns showAdminPanel(), because the prototype chain lookup finds isAdmin: true before reaching undefined.
Real-world libraries affected
This vulnerability has been found in some of the most widely downloaded JavaScript packages:
- lodash (CVE-2019-10744) —
_.merge,_.defaultsDeep, and related functions; over 20 million weekly downloads at the time of disclosure. - jQuery (CVE-2019-11358) —
$.extend(true, ...)deep merge. - Handlebars (CVE-2021-23369) — pollution via template compilation leading to remote code execution.
What you will do in this lab
Using the interactive panel to your right, you will:
- Study the vulnerable recursive merge function and identify exactly which line enables the attack.
- Craft a JSON payload containing the
__proto__key that, when passed to the merge function, injectsisAdmin: trueonto the shared prototype object. - Observe in real time how a new object created after the merge — one that has never had
isAdminset on it — suddenly evaluatesnewUser.isAdminastrue. - Learn the remediation patterns that eliminate this vulnerability at its root.
The interactive panel simulates the merge in your browser — without actually polluting the real Object.prototype — so you can experiment safely and observe exactly what happens at each step.