Prototype Pollution
Prototype Pollution is a critical JavaScript vulnerability that allows attackers to inject properties into object prototypes, affecting all objects in the application. It can lead to remote code execution, authentication bypass, and DOM-based XSS attacks.
What Is Prototype Pollution?
Prototype Pollution is a vulnerability unique to JavaScript and its prototype-based inheritance model. In JavaScript, objects inherit properties and methods from their prototype chain. When an application allows user input to modify object properties without proper validation, attackers can inject malicious properties into Object.prototype or other built-in prototypes, causing these properties to appear on all objects throughout the application.
The vulnerability typically occurs in operations that merge or clone objects, especially when using recursive functions that traverse object properties. Common vulnerable patterns include deep merge utilities, object cloning functions, and JSON parsing that improperly handles special keys like __proto__, constructor, or prototype. When user-controlled input reaches these functions, attackers can craft payloads that modify the prototype chain.
Prototype Pollution has become increasingly prevalent in modern web applications, particularly in Node.js backends and Single Page Applications (SPAs). Libraries like Lodash, jQuery, and many npm packages have had prototype pollution vulnerabilities patched over the years. OWASP includes prototype pollution as part of A08:2021 – Software and Data Integrity Failures, recognizing its potential for severe impact when exploited in combination with other application features or vulnerable dependencies.
How It Works
The application receives user input that will be used to update, merge, or clone objects. This could be JSON data from an API request, query parameters, form submissions, or configuration files. The input is typically expected to contain property names and values.
The application uses a deep merge, recursive clone, or similar function to combine the user input with existing objects. The function traverses object properties recursively without checking for dangerous keys like __proto__, constructor, or prototype.
The attacker sends a specially crafted object containing __proto__ or constructor.prototype keys with malicious properties. For example: {"__proto__": {"isAdmin": true}} or {"constructor": {"prototype": {"polluted": "value"}}}.
The vulnerable function assigns properties to Object.prototype or other prototypes instead of treating them as normal object keys. This pollution affects all objects in the application that inherit from that prototype, including objects created after the pollution occurs.
The injected properties now exist on all objects. This can bypass security checks (if the application checks for obj.isAdmin), trigger XSS (if polluted properties are rendered in DOM), cause denial of service, or enable remote code execution if the polluted properties are used in dangerous operations like child_process.spawn().
Vulnerable Code Example
const express = require('express');
const app = express();
app.use(express.json());
// VULNERABLE: Unsafe deep merge function
function merge(target, source) {
for (let key in source) {
if (typeof source[key] === 'object') {
if (!target[key]) target[key] = {};
// PROBLEM: Recursively merges without checking for
// dangerous keys like __proto__ or constructor
merge(target[key], source[key]);
} else {
target[key] = source[key];
}
}
return target;
}
app.post('/api/settings', (req, res) => {
const userSettings = {};
// User input is merged without validation
merge(userSettings, req.body);
res.json({ success: true, settings: userSettings });
});
// Attack payload:
// POST /api/settings
// {"__proto__": {"isAdmin": true}}
//
// Now ALL objects have obj.isAdmin === true
// const user = {};
// console.log(user.isAdmin); // true (polluted!)Secure Code Example
const express = require('express');
const app = express();
app.use(express.json());
// SECURE: Safe merge with prototype pollution protection
function safeMerge(target, source) {
const blocklist = ['__proto__', 'constructor', 'prototype'];
for (let key in source) {
// Block dangerous keys
if (blocklist.includes(key)) continue;
// Only process own properties
if (!source.hasOwnProperty(key)) continue;
if (typeof source[key] === 'object' && source[key] !== null) {
if (!target[key]) {
// Create object with no prototype to avoid pollution
target[key] = Object.create(null);
}
safeMerge(target[key], source[key]);
} else {
target[key] = source[key];
}
}
return target;
}
app.post('/api/settings', (req, res) => {
// Use Map instead of plain object for better isolation
const userSettings = Object.create(null);
safeMerge(userSettings, req.body);
res.json({ success: true, settings: userSettings });
});
// Alternative: Use Object.freeze() to prevent modifications
Object.freeze(Object.prototype);
// The attack payload is now blocked:
// {"__proto__": {"isAdmin": true}} -> __proto__ key is skipped
// Objects remain unpollutedTypes of Prototype Pollution
Server-Side Prototype Pollution
Occurs in Node.js backend applications and can have severe consequences. Attackers can pollute prototypes to achieve Remote Code Execution (RCE) by injecting properties that are later used in dangerous operations like child_process.spawn() or require(). They can also bypass security checks by polluting authentication-related properties, manipulate application logic, or cause denial of service by overwriting critical methods. Server-side prototype pollution often has a wider blast radius than client-side variants.
Client-Side Prototype Pollution
Happens in browser JavaScript code and can lead to DOM-based XSS attacks, especially when polluted properties are rendered into the DOM without sanitization. Attackers exploit gadget chains in popular JavaScript libraries where prototype pollution triggers vulnerable code paths. Common scenarios include polluting properties that control DOM manipulation, bypassing XSS filters and sanitizers, or exploiting vulnerable template rendering engines. Client-side pollution is often easier to discover but can still have significant security impact.
Prototype Pollution via JSON
A specific variant where pollution occurs during JSON parsing or processing. When applications use JSON.parse() followed by unsafe object manipulation, or when custom JSON parsers don't properly handle __proto__ keys in the JSON payload. This is particularly dangerous in APIs that accept JSON bodies, configuration file parsers, or data import features. The payload {"__proto__": {"polluted": true}} is a common attack vector. Some older libraries had vulnerabilities where even JSON.parse() itself could be exploited in combination with prototype pollution gadgets.
Impact
Prototype Pollution can have severe security implications depending on how the polluted properties are used within the application. The impact ranges from information disclosure to complete system compromise.
In Node.js environments, attackers can pollute properties that are later used in code execution contexts. For example, polluting shell or env properties that get passed to child_process.spawn(), or injecting into module resolution paths. This can allow attackers to execute arbitrary system commands on the server, leading to full server compromise.
Client-side prototype pollution can be chained with DOM XSS attacks. If the application reads polluted properties and renders them into the DOM without proper sanitization, attackers can inject malicious scripts. This is especially dangerous in frameworks that use property-based rendering or when prototype pollution bypasses XSS sanitization logic.
Attackers can pollute prototypes with properties that break application logic, cause infinite loops, or trigger exceptions throughout the codebase. Overwriting critical methods like toString() or valueOf() can crash the application or make it unresponsive. In some cases, pollution can exhaust server memory or CPU resources.
If the application checks user privileges using object properties (e.g., if (user.isAdmin)), attackers can pollute Object.prototype.isAdmin = true to grant themselves elevated privileges. This can bypass role-based access controls, authentication checks, and permission validations, allowing unauthorized access to sensitive resources and administrative functions.
Prevention Checklist
Never use recursive merge functions that don't validate property keys. Always implement a blocklist for dangerous keys like __proto__, constructor, and prototype. Use Object.hasOwnProperty() to ensure you're only processing own properties, not inherited ones. Consider using libraries with built-in prototype pollution protection or well-tested alternatives like structured clone.
When creating objects that will store user-controlled keys and values, use Object.create(null) to create objects without a prototype chain. This eliminates the inheritance of Object.prototype properties and prevents prototype pollution attacks. Alternatively, use Map instead of plain objects for key-value storage, as Maps are not affected by prototype pollution.
Use Object.freeze(Object.prototype) to make prototypes immutable and prevent modification. While this is a defense-in-depth measure that won't stop all attacks, it can prevent certain exploitation techniques. Be aware that freezing prototypes may break some legacy code or third-party libraries that attempt to extend built-in prototypes.
Implement strict input validation for object property names. Use allowlists to specify which keys are permitted, and reject any input containing dangerous keys. When processing JSON, parse and validate the structure before passing it to merge or clone operations. Never trust user input to define object structure.
Many popular npm packages have had prototype pollution vulnerabilities that were subsequently patched. Regularly audit your dependencies using tools like npm audit, yarn audit, or Snyk. Subscribe to security advisories for your critical dependencies. Replace deprecated or unmaintained packages that have known vulnerabilities.
Implement schema validation libraries like Joi, Yup, or JSON Schema to enforce strict structure on incoming data. Define explicit schemas that specify allowed properties, their types, and nesting levels. This prevents attackers from injecting unexpected properties or deeply nested malicious payloads. Schema validation acts as a strong defense layer before data reaches vulnerable code paths.
Real-World Examples
Lodash CVE-2019-10744
A critical prototype pollution vulnerability was discovered in Lodash, one of the most popular JavaScript utility libraries with billions of downloads. The vulnerability existed in functions like defaultsDeep, merge, and setWith, allowing attackers to modify Object.prototype. This affected countless Node.js applications and frontend projects. The vulnerability was patched in version 4.17.12.
jQuery CVE-2019-11358
jQuery's $.extend(true, ...) deep merge function was found vulnerable to prototype pollution. An attacker could craft malicious payloads to pollute Object.prototype in applications using jQuery for data merging. Given jQuery's massive adoption across the web, this vulnerability had enormous potential impact. The fix was released in jQuery 3.4.0, but many legacy applications remain vulnerable.
Kibana RCE via Prototype Pollution
Security researchers discovered a prototype pollution vulnerability in Kibana (Elasticsearch's visualization platform) that could be chained to achieve Remote Code Execution. By polluting prototypes through the Timelion feature and leveraging the polluted properties in a subsequent operation, attackers could execute arbitrary code on the server. This demonstrated how prototype pollution can escalate to RCE in real-world applications.
npm package registry incidents
Multiple npm packages including minimist, yargs-parser, and deep-extend were found vulnerable to prototype pollution attacks. These packages are widely used for parsing command-line arguments and merging configuration objects. The vulnerabilities allowed attackers to inject malicious properties through CLI arguments or configuration files, potentially affecting millions of Node.js applications. The npm ecosystem has since improved its security scanning to detect these issues.
Ready to Test Your Knowledge?
Put what you have learned into practice. Try identifying and fixing Prototype Pollution vulnerabilities in our interactive coding challenges, or explore more security guides to deepen your understanding.