Prototype Pollution: How to Find and Exploit It in Bug Bounty
Prototype pollution is a JavaScript-specific bug where attacker-controlled keys like __proto__, constructor, and prototype reach an object merge or property assignment and change Object.prototype for the whole application. Suddenly every object “inherits” a property you planted.
It stays high on search intent because modern apps merge untrusted JSON constantly — query strings, config objects, and API bodies all flow into deep-merge helpers.
Two flavors to know
Client-side: a browser script merges URL or DOM input into an object, poisoning Object.prototype. This can escalate to DOM XSS when a sink later reads a polluted property (for example a gadget that trusts config.src or options.template).
Server-side: a Node.js endpoint deep-merges request JSON into an object. Pollution can flip security-relevant defaults, enable denial of service, or — with the right gadget — reach command execution.
Spot the vulnerable pattern
Look for code (or behavior) that:
- Deep-merges untrusted input:
merge(target, req.body),_.merge, customextend - Parses query strings into nested objects (
a[b][c]=1) - Builds objects from user-controlled key paths
- Copies “options” objects from clients into internal config
Any place where a key name comes from the attacker is a candidate.
Safe proof workflow
Prove pollution without breaking the app:
- Send a benign marker via
__proto__, for example a JSON body of{"__proto__":{"bugflarePoc":"1"}}or a query like?__proto__[bugflarePoc]=1. - Find a reflection that reveals the planted property appearing on an unrelated object — a debug echo, a rendered template value, or a response field that now carries your marker.
- For client-side, watch for a known gadget turning the polluted property into script execution in your own browser.
Use a unique marker so you can attribute the effect. Do not pollute properties that could corrupt other users' sessions on shared infrastructure.
From pollution to impact
Pollution alone is a bug; a gadget makes it severe:
- Client-side gadget → DOM XSS: a script reads a polluted property and injects HTML or a script src.
- Server-side gadget → privilege or logic change: a default like
isAdminorallowis read via prototype lookup. - Denial of service: polluting a property every object reads can crash request handling.
Document the exact gadget. Triagers reward a demonstrated sink, not just a reflected marker.
Common false positives
- A property you set on your own object only (no prototype leakage)
- Frameworks that already block
__proto__keys - Reflection that never reaches a security-relevant sink
Report structure
Show the injection request, the object that inherited your property, and the concrete consequence (XSS, altered authorization, or DoS). Recommend blocking dangerous keys, using Object.create(null) for maps, freezing prototypes, and validating input with a schema.
Defensive checklist
- Reject
__proto__,constructor, andprototypekeys at the parser boundary. - Avoid unsafe recursive merges on untrusted input.
- Use
Mapor null-prototype objects for user-controlled dictionaries. - Validate request bodies against a strict schema.
- Add tests that feed prototype keys into every merge helper.
Prototype pollution rewards hunters who understand JavaScript internals. Find the merge, plant a marker, then chase a gadget — that is how a “weird” bug becomes a critical.
Original Bugflare guide informed by PortSwigger prototype pollution material.