How Prototype Pollution Works in JavaScript
Prototype pollution is a critical JavaScript vulnerability that
occurs when an attacker is able to inject or modify properties on shared
base object prototypes, most notably Object.prototype.
Because JavaScript relies on prototypical inheritance, altering a base
prototype automatically propagates those malicious properties to almost
every object in the application runtime. This article explores the core
mechanics of prototype pollution, the coding patterns that leave
applications exposed, the potential security impacts, and how to
effectively prevent attacks.
JavaScript’s Prototypal Inheritance Model
In JavaScript, objects inherit properties and methods from other
objects via a prototype chain. When code queries a property on an
object, the runtime first checks the object itself. If the property is
not found, the engine traverses up the prototype chain until it reaches
the root prototype, Object.prototype, before returning
undefined.
Every standard object created using object literal syntax
({}) or the new Object() constructor links
back to Object.prototype. Consequently, adding or altering
a property on Object.prototype immediately alters the
behavior of all downstream objects that do not already have an own
property with the same name.
The Mechanism of Prototype Pollution
Prototype pollution happens when an application unsafely merges, clones, or assigns user-controlled input into existing objects without properly validating keys. Special properties in JavaScript expose an object’s prototype linkage:
__proto__: Directly accesses the object’s prototype.constructor.prototype: Accesses the prototype via the object’s constructor function.
When an application processes an attacker-controlled JSON payload or
query string containing these properties, a vulnerable recursive merge
or assignment function might interpret __proto__ as an
instructional path rather than a literal string key.
For example, consider a vulnerable recursive merge function processing the following JSON input:
{
"__proto__": {
"isAdmin": true
}
}If the function traverses into target["__proto__"], it
resolves the reference to Object.prototype. When it assigns
target["__proto__"]["isAdmin"] = true, it writes directly
to the shared base prototype. From that point forward, any empty or
unrelated object in the runtime will evaluate ({}).isAdmin
as true.
Common Attack Vectors and Consequences
Prototype pollution can lead to several security impacts depending on how the application relies on uninitialized object properties:
- Authentication and Authorization Bypass:
Applications often check user roles using logic such as
if (user.isAdmin). IfisAdminis not explicitly defined on the user object, the runtime falls back to the polluted prototype, granting elevated privileges. - Denial of Service (DoS): Overwriting built-in
methods on
Object.prototype(such astoStringorvalueOf) with non-function values causes the application to throw unhandled exceptions during standard operations, crashing the server. - Remote Code Execution (RCE): In Node.js
environments, internal modules and third-party libraries frequently rely
on options objects passed to child processes or template engines.
Polluting specific properties (e.g.,
shell,NODE_OPTIONS, or template helpers) can trick the runtime into executing arbitrary system commands.
Prevention and Mitigation
Securing applications against prototype pollution requires strict handling of dynamic object operations and prototype configurations:
- Block Dangerous Keys: Validate and sanitize input
keys in deep merge, clone, and path-traversal utilities. Reject or
ignore keys matching
__proto__,constructor, andprototype. - Use Prototype-less Objects: When creating lookup
tables or data maps, use
Object.create(null)instead of{}. Objects created without a prototype have no__proto__chain and are immune to prototype-level modifications. - Utilize
Mapfor Key-Value Storage: Prefer the modernMapcollection for dynamic keys instead of standard plain objects. - Freeze the Prototype: Call
Object.freeze(Object.prototype)during application startup. Freezing prevents any additions, deletions, or modifications to the root prototype, effectively neutralizing prototype pollution attempts. - Use Secure Utility Libraries: Ensure helper libraries (such as Lodash) are kept up to date, as modern versions include built-in safeguards against prototype pollution.