BIPI
BIPI

Prototype Pollution in JavaScript: Source, Sink, Gadgets, and RCE Chains

Cybersecurity

From client-side query string pollution to server-side RCE via lodash, ejs, and Node child_process gadgets. The 2024 prototype pollution playbook.

By Arjun Raghavan, Security & Systems Lead, BIPI · November 25, 2024 · 12 min read

#prototype-pollution#javascript#node.js#pentesting#rce

Prototype pollution feels like a curiosity until you find the right gadget. Then a single polluted property turns into XSS on every page, an authorization bypass, or full RCE on a Node server. The class has been studied since 2018 and still ships in fresh libraries each year.

How Pollution Happens

  • Recursive merge functions that copy proto keys
  • Object.assign with attacker-controlled property names
  • Query string parsers that build nested objects from bracketed syntax
  • JSON.parse output passed directly to a merge without sanitization
  • Setting object key to value where the key is untrusted

Client-Side Pollution Sources

URL query strings are the canonical client-side source. A library that parses proto bracket isAdmin equals true and merges it into config pollutes Object.prototype for every subsequent object. PortSwigger DOM Invader has a dedicated prototype pollution mode that finds these in minutes.

Client-Side Gadgets

  1. jQuery extend variants that pull options from Object.prototype
  2. Analytics scripts that build script src from polluted defaults
  3. AngularJS expression sandboxing escapes via polluted constructor
  4. Ad scripts and tag managers reading config from prototype
  5. innerHTML sinks fed by templates with polluted helpers

Server-Side Pollution to RCE

On Node, child_process spawn and exec accept an options object. If you can pollute Object.prototype.shell with a shell path and Object.prototype.env with a payload, a downstream spawn call inherits your values. The 2022 lodash CVEs and many Express middleware bugs followed this exact path.

Template Engine Gadgets

  • EJS: pollute outputFunctionName to inject arbitrary code into compiled templates
  • Handlebars: pollute helpers map to override safe helpers with attacker functions
  • Pug: pollute compileDebug and self to inject expression context
  • Lodash template: similar outputFunctionName gadget

Detection Methodology

Send a JSON body with a proto key that sets a marker property, then probe an endpoint that echoes object keys. Burp prototype pollution scanner and the server-side variant from PortSwigger Research cover most cases. Manual testing wins on custom merges.

Prototype pollution is rarely the bug. It is the primitive. The bounty is in the gadget you chain to.

Real CVEs Worth Studying

  1. CVE-2019-10744 lodash defaultsDeep pollution
  2. CVE-2020-7598 minimist pollution via argv parsing
  3. CVE-2021-44906 minimist follow-up
  4. CVE-2022-25883 semver pollution-adjacent regex
  5. CVE-2023-26136 tough-cookie prototype pollution

Remediation

  • Use Object.create with null prototype for any map that holds untrusted keys
  • Freeze Object.prototype at app boot
  • Reject proto, constructor, and prototype keys at the parser layer
  • Use Map instead of plain objects for dynamic key value stores
  • Pin and audit merge libraries; many have shipped multiple bypasses
Object.freeze
one-line mitigation that breaks most chains
5 CVEs
in lodash and minimist alone since 2019
null proto
the safe map you should have been using

Read more field notes, explore our services, or get in touch at info@bipi.in. Privacy Policy · Terms.