Understanding Prototype Pollution
Prototype pollution is a critical security vulnerability that exploits JavaScript's prototype-based inheritance model. Attackers manipulate an object's prototype, affecting all instances derived from it. This can lead to severe consequences, including cross-site scripting (XSS), unauthorized property injection, and even distributed denial-of-service (DDoS) attacks.
Key Points
- Prototype pollution occurs when an attacker modifies an object's prototype, impacting all instances of that object.
- JavaScript-specific risk: Most prevalent in JavaScript due to its prototype-based inheritance, but can affect any system using similar models.
- Security impact: Enables XSS, property injection, and DDoS attacks by altering shared object behaviors.
- Mitigation requires: Secure coding practices, input validation, and dependency management.
How Prototype Pollution Works
Prototype pollution exploits the dynamic nature of JavaScript's prototype chain. By injecting malicious properties into an object's prototype, attackers can alter the behavior of all objects sharing that prototype.
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.
Key Concepts:
- Prototype chain: A linked hierarchy of objects where property lookups propagate upward.
__proto__: A non-standard accessor for an object's prototype (avoid in production code).Object.prototype: The root prototype from which most objects inherit.
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__.
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
Exploitation Scenarios
Prototype pollution rarely acts alone. Attackers combine it with other techniques to achieve:
1. Cross-Site Scripting (XSS)
- Mechanism: Pollute prototypes with malicious
toStringorvalueOfmethods. - Impact: Execute arbitrary JavaScript when objects are converted to strings.
2. Property Injection
- Mechanism: Add unexpected properties to all objects (e.g.,
isAdmin: true). - Impact: Bypass authentication or authorization checks.
3. Denial of Service (DoS)
- Mechanism: Pollute prototypes with computationally expensive methods.
- Impact: Degrade application performance or crash the runtime.
Real-World Example: Lodash Vulnerability (CVE-2019-10744)
The popular Lodash library was vulnerable to prototype pollution in its defaultsDeep function, allowing attackers to inject properties into Object.prototype.
Mitigation Strategies
For Developers: Secure Coding Practices
-
Avoid unsafe merges:
- Use libraries like
lodash.mergewith_.defaultsDeep(patched versions). - Implement safe object property assignment:
function safeMerge(target, source) { Object.keys(source).forEach(key => { if (key === '__proto__' || key === 'constructor') 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; }
- Use libraries like
-
Use immutable patterns:
- Freeze critical prototypes:
Object.freeze(Object.prototype); - Use
Object.create(null)to create objects without prototypes.
- Freeze critical prototypes:
-
Input validation:
- Sanitize keys in user-controlled objects.
- Use allowlists for property names.
-
Dependency management:
- Audit third-party libraries for prototype pollution vulnerabilities.
- Keep dependencies updated.
-
Security headers:
- Implement Content Security Policy (CSP) to mitigate XSS risks.
For Pentesters: Detection Techniques
- Fuzzing:
- Test object property assignments with payloads like:
{"__proto__": {"evil": "payload"}}
- Test object property assignments with payloads like:
- Static analysis:
- Scan code for unsafe merge functions or prototype access.
- Dynamic analysis:
- Monitor prototype chain modifications during runtime.
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. |
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).
Learn More
Expand your knowledge with these authoritative resources: