Understanding Prototype Pollution
Prototype pollution is a JavaScript-specific security vulnerability that exploits the language's prototype-based inheritance model. By manipulating an object's prototype, attackers can alter the behavior of all objects sharing that prototype, leading to severe consequences like cross-site scripting (XSS), unauthorized property injection, or denial-of-service (DoS) attacks.
This vulnerability highlights the risks of JavaScript's dynamic nature, where objects inherit properties and methods through a chain of prototypes. Understanding how prototype pollution works—and how to prevent it—is critical for securing modern web applications.
Key Points
- Prototype pollution occurs when attackers modify an object's prototype, affecting all derived instances.
- JavaScript-specific risk: Most prevalent in JavaScript due to its prototype-based inheritance but can impact any system with similar models.
- Security impact: Enables XSS, property injection, and DoS attacks by altering shared object behaviors.
- Mitigation requires: Secure coding practices, input validation, and dependency management.
How Prototype Pollution Works
Prototype pollution exploits JavaScript's prototype chain by injecting malicious properties into an object's prototype. This alters the behavior of all objects sharing that prototype, often without the developer's knowledge.
JavaScript's Prototype Model
JavaScript uses prototypal inheritance, where objects inherit properties and methods from their prototype. This creates a chain of prototypes that the JavaScript engine traverses when accessing properties.
Core Concepts:
- Prototype chain: A linked hierarchy where property lookups propagate upward until the property is found or the chain ends.
__proto__: A non-standard accessor for an object's prototype (avoid in production code).Object.prototype: The root prototype from which most objects inherit.constructor: A reference to the function that created the object's prototype.
Attack Mechanism
Attackers exploit unsafe object property assignments to pollute the prototype chain. This typically occurs when:
- User-controlled input is merged into objects without validation.
- Code uses unsafe patterns like
obj[key] = valuewherekeycan be__proto__orconstructor.
Example: Basic Prototype Pollution
// Vulnerable merge function
function merge(target, source) {
for (let key in source) {
if (typeof source[key] === 'object') {
if (!target[key]) target[key] = {};
merge(target[key], source[key]);
} else {
target[key] = source[key];
}
}
return target;
}
// Attacker's payload
const maliciousPayload = JSON.parse('{"__proto__": {"isAdmin": true}}');
merge({}, maliciousPayload);
// All objects now inherit the polluted property
console.log({}.isAdmin); // true
Note: The above example demonstrates how a single unsafe merge can compromise the entire application.
Exploitation Scenarios
Prototype pollution rarely acts alone. Attackers combine it with other techniques to achieve malicious goals:
1. Cross-Site Scripting (XSS)
- Mechanism: Pollute prototypes with malicious
toStringorvalueOfmethods. - Impact: Execute arbitrary JavaScript when objects are converted to strings (e.g., in template engines or
alert()calls). - Example:
const payload = JSON.parse('{"__proto__": {"toString": function() { return "<script>alert(1)</script>"; }}}'); merge({}, payload); console.log({}.toString()); // Executes XSS
2. Property Injection
- Mechanism: Add unexpected properties to all objects (e.g.,
isAdmin: true). - Impact: Bypass authentication or authorization checks in applications that rely on object properties.
- Example:
// Application checks user.isAdmin const user = {}; console.log(user.isAdmin); // true (after pollution)
3. Denial of Service (DoS)
- Mechanism: Pollute prototypes with computationally expensive methods or infinite loops.
- Impact: Degrade application performance or crash the runtime.
- Example:
const payload = JSON.parse('{"__proto__": {"valueOf": function() { while(true); }}}'); merge({}, payload); // Any operation using valueOf() will hang
Real-World Example: Lodash Vulnerability (CVE-2019-10744)
The popular Lodash library was vulnerable to prototype pollution in its _.defaultsDeep function. Attackers could inject properties into Object.prototype, affecting all objects in the application. This was patched in Lodash v4.17.12.
Mitigation Strategies
For Developers: Secure Coding Practices
1. Avoid Unsafe Merges
- Use patched libraries (e.g., Lodash v4.17.12+).
- Implement safe object property assignment:
function safeMerge(target, source) { Object.keys(source).forEach(key => { if (key === '__proto__' || key === 'constructor' || key === 'prototype') return; if (typeof source[key] === 'object' && source[key] !== null) { if (!target[key]) target[key] = {}; safeMerge(target[key], source[key]); } else { target[key] = source[key]; } }); return target; }
2. Use Immutable Patterns
- Freeze critical prototypes:
Object.freeze(Object.prototype); - Create objects without prototypes:
const obj = Object.create(null);
3. Input Validation
- Sanitize keys in user-controlled objects.
- Use allowlists for property names:
const allowedKeys = new Set(['name', 'email', 'age']); function sanitize(obj) { return Object.keys(obj).reduce((acc, key) => { if (allowedKeys.has(key)) acc[key] = obj[key]; return acc; }, {}); }
4. Dependency Management
- Audit third-party libraries for prototype pollution vulnerabilities using tools like:
npm auditsnyk test
- Keep dependencies updated.
5. Security Headers
- Implement Content Security Policy (CSP) to mitigate XSS risks:
Content-Security-Policy: script-src 'self'; object-src 'none';
For Pentesters: Detection Techniques
1. Fuzzing
Test object property assignments with payloads like:
{"__proto__": {"evil": "payload"}}
{"constructor": {"prototype": {"isAdmin": true}}}
2. Static Analysis
- Scan code for:
- Unsafe merge functions (e.g.,
_.merge,Object.assign). - Direct prototype access (e.g.,
__proto__,constructor.prototype).
- Unsafe merge functions (e.g.,
- Tools: ESLint with
no-prototype-builtinsrule.
3. Dynamic Analysis
- Monitor prototype chain modifications during runtime using:
- Chrome DevTools:
Object.observe(deprecated but useful for debugging). - Custom proxies to log property access.
- Chrome DevTools:
JavaScript Prototype Fundamentals
Understanding prototypes is essential to grasp the vulnerability. Here’s a comparison of JavaScript's inheritance models:
| Feature | Class-Based Inheritance | Prototype-Based Inheritance |
|---|---|---|
| Definition | Blueprints for object creation. | Objects inherit directly from other objects. |
| Inheritance | Hierarchical (parent-child). | Delegation-based (prototype chain). |
| Flexibility | Rigid structure. | Dynamic property addition/removal. |
| Performance | Slightly faster property access. | More memory-efficient for shared properties. |
| Syntax | class, extends, super. | Object.create(), __proto__, prototype. |
| Use Case | Predictable, structured code. | Dynamic, flexible applications. |
Key Methods:
Object.create(proto): Creates a new object with the specified prototype.Object.getPrototypeOf(obj): Returns the prototype of an object.Object.setPrototypeOf(obj, proto): Sets the prototype (avoid in performance-critical code).Object.freeze(obj): Prevents modifications to an object and its prototype.
Learn More
Expand your knowledge with these authoritative resources: