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:

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:

  1. Authentication and Authorization Bypass: Applications often check user roles using logic such as if (user.isAdmin). If isAdmin is not explicitly defined on the user object, the runtime falls back to the polluted prototype, granting elevated privileges.
  2. Denial of Service (DoS): Overwriting built-in methods on Object.prototype (such as toString or valueOf) with non-function values causes the application to throw unhandled exceptions during standard operations, crashing the server.
  3. 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: