Improperly Controlled Modification of Object Prototype Attributes (Prototype Pollution)
Prototype pollution is a vulnerability in JavaScript applications where an attacker can modify the prototype of a base object like Object.prototype by…
Prototype pollution is a vulnerability in JavaScript applications where an attacker can modify the prototype of a base object (like Object.prototype) by injecting malicious properties through user-controlled input. Because all JavaScript objects inherit from their prototype, poisoning the prototype can affect the behavior of every object in the application, leading to unexpected code execution, logic bypass, or data exposure.
02How It Happens
JavaScript objects inherit properties from their prototype chain. When an application recursively merges, clones, or assigns properties from untrusted input (such as JSON data, query parameters, or form fields) without validating property names, an attacker can use special keys like __proto__, constructor, or prototype to reach and modify the base Object.prototype. Once the prototype is polluted, any code that reads or relies on object properties will see the attacker's injected values, even if those properties were never explicitly set on the object itself.
This typically occurs in utility functions that deep-merge configuration objects, parse nested data structures, or dynamically assign properties based on user input. The vulnerability is particularly dangerous because the pollution is global—it affects all objects created after the attack, not just a single instance.
03Real-World Impact
Prototype pollution can lead to authentication bypass (by injecting an isAdmin property into the prototype), privilege escalation, denial of service (by polluting critical properties), or remote code execution if the polluted properties are used in dangerous contexts like template rendering or function calls. In some cases, attackers can override security-critical functions or disable validation checks by poisoning the prototype with malicious implementations.
04Vulnerable & Fixed Patterns
Vulnerable pattern
import json
def merge_config(base_config, user_input):
"""Recursively merge user input into base config."""
for key, value in user_input.items():
if isinstance(value, dict) and key in base_config:
merge_config(base_config[key], value)
else:
base_config[key] = value
return base_config
user_data = json.loads('{"__proto__": {"isAdmin": true}}')
config = {"isAdmin": false}
merge_config(config, user_data)
Why it's vulnerable: Python's __proto__ is not a direct prototype chain mechanism like JavaScript, but this pattern shows the conceptual flaw: unvalidated keys are merged into objects without checking whether they are control properties. In JavaScript, this would directly pollute the prototype.
Fixed pattern
import json
BLOCKED_KEYS = {"__proto__", "constructor", "prototype"}
def merge_config(base_config, user_input):
"""Recursively merge user input, blocking dangerous keys."""
for key, value in user_input.items():
if key in BLOCKED_KEYS:
continue # Skip prototype-pollution keys
if isinstance(value, dict) and isinstance(base_config.get(key), dict):
merge_config(base_config[key], value)
else:
base_config[key] = value
return base_config
user_data = json.loads('{"__proto__": {"isAdmin": true}, "theme": "dark"}')
config = {"isAdmin": false}
merge_config(config, user_data)
Why it's vulnerable: While PHP's object model differs from JavaScript, this pattern of unvalidated recursive merging can still lead to unexpected property injection if the merged array is later used to set object properties or influence control flow.
Validate and allowlist property names: Only permit keys that match a known, safe set of configuration properties. Reject or skip any key that is not explicitly expected.
Block dangerous keys explicitly: Maintain a blocklist of prototype-chain keys (__proto__, constructor, prototype, __constructor__) and reject them at merge time.
Use Object.create(null) for untrusted data: When creating objects from user input, use Object.create(null) to create objects without a prototype chain, preventing pollution.
Avoid recursive merging of untrusted input: If possible, use immutable data structures or avoid deep merging altogether. If merging is necessary, do it in a controlled, validated manner.
Use libraries with built-in protections: Modern utility libraries (e.g., lodash with prototype-pollution patches) include safeguards; ensure they are up to date.
Implement input schema validation: Use a schema validator (JSON Schema, Zod, etc.) to enforce the structure and allowed keys of incoming data before processing.
06Signs You May Already Be Affected
Look for unexpected properties appearing on objects throughout your application, particularly security-related properties like isAdmin, role, or permissions that you did not explicitly set. Check application logs for unusual key names in JSON payloads (especially __proto__, constructor, or prototype). If authentication or authorization logic suddenly fails or behaves inconsistently across different parts of your application, prototype pollution may be the cause.