Prototype Pollution: From Property Injection to Remote Code Execution
Cybersecurity
Prototype pollution in JavaScript allows attackers to inject properties onto Object.prototype and affect all downstream property lookups. A deep technical guide from basic exploitation to full RCE chains in Node.js.
By Arjun Raghavan, Security & Systems Lead, BIPI · June 18, 2025 · 11 min read
JavaScript's prototype chain is a design feature that becomes a security vulnerability when user-controlled input can set properties on Object.prototype. Because all JavaScript objects inherit from Object.prototype, poisoning it affects every property lookup in the application's runtime — including internal framework properties that control execution behaviour.
Understanding the Vulnerability
The simplest vulnerable pattern is a deep merge function that recursively copies properties from a source object to a target. If source contains a __proto__ key, the merge sets properties on Object.prototype. After this merge, every object in the process inherits the injected properties unless it has its own property overriding the prototype. This affects all subsequent code in the same process.
Finding Prototype Pollution
- Grep the codebase for recursive merge, deep merge, or extend functions.
- Look for user-controlled input processed by lodash.merge, jQuery.extend, or custom merge utilities.
- Test query string parameters with /?__proto__[isAdmin]=true or JSON body with {"__proto__": {"isAdmin": true}}.
- Test property access via constructor: /?constructor[prototype][isAdmin]=true.
- Use property-injection via URL path: /api/user?a[__proto__][role]=admin.
Exploiting Prototype Pollution for Privilege Escalation
After confirming pollution is possible, the impact depends on which properties the application checks. Common targets: isAdmin, role, debug, authorized, _options. If the application does if (!options.disableAuth) { requireAuth(); }, polluting {disableAuth: true} bypasses authentication for every request. Look for template rendering, shell execution, or dynamic require() calls that read from objects that inherit from the polluted prototype.
Prototype pollution is not just an application-layer bug. In Node.js, it can escalate from property injection to arbitrary code execution via child_process.spawn option inheritance.
Node.js RCE via Prototype Pollution
Node.js's child_process.spawn reads options from an options object. If __proto__.shell is set to true and __proto__.env.NODE_OPTIONS is set to --require /tmp/malicious.js, any subsequent spawn call will execute the attacker-controlled module. The exploit chain: find prototype pollution, set shell and env properties on Object.prototype, trigger any code path that calls child_process.spawn or execFile.
Client-Side Prototype Pollution
Client-side prototype pollution typically leads to XSS rather than RCE. The DOMPurify bypass was a prototype pollution chain where polluting isForcedType caused DOMPurify to skip sanitisation. Modern gadget research focuses on polluting properties used by template engines like Handlebars and Lodash template — both have confirmed gadgets that achieve script execution from prototype pollution alone.
Mitigations
- Use Object.create(null) for data structures that merge user input — null-prototype objects do not inherit from Object.prototype.
- Validate and reject __proto__, constructor, and prototype keys in all merge and deep copy functions.
- Use structuredClone() instead of custom merge functions where possible.
- Enable Node.js --frozen-intrinsics flag in production to prevent modification of built-in prototypes.
- Use JSON schema validation to reject unexpected nested keys before they reach merge logic.
Read more field notes, explore our services, or get in touch at info@bipi.in. Privacy Policy · Terms.