Runbook: Prototype pollution

Prototype pollution injects properties into Object.prototype, so every object in the runtime inherits an attacker-chosen value. On its own it is often just a nuisance; its weight comes from the gadget that later reads the polluted property, which can turn it into DOM XSS in the browser or remote code execution on the server. This runbook separates the two sides, since detection and escalation differ.

Prerequisites

  • Burp Suite with DOM Invader (built into the embedded browser) for the client side.

  • The Server-Side Prototype Pollution Scanner extension for the server side.

  • An understanding of which merge, clone, or query-parsing operations the application runs on user input, since those are where pollution sources live.

Phase 1: Client-side detection (CSPP)

A source is any place a user-controlled key reaches a recursive merge or a query/hash parser without sanitising __proto__. Probe through the URL:

?__proto__[polluted]=yes
#__proto__[polluted]=yes
?constructor[prototype][polluted]=yes

Then in the browser console check whether the prototype took the property:

Object.prototype.polluted   // "yes" confirms pollution

DOM Invader automates this: enable it, let it flag sources, then use Scan for Gadgets to find a property that flows to a sink such as innerHTML. Where it finds one it offers a generated exploit combining source and gadget.

Phase 2: Client-side escalation

A source is only useful with a gadget. Look for a sink that reads a configurable property off an object without the object defining it, so the polluted prototype supplies the value: script src, innerHTML, an eval-like call, or a sanitiser configuration. Polluting that property with a script payload turns the source into DOM XSS. Third-party libraries and browser APIs are common gadget homes, so test them even when the application’s own code looks clean.

Phase 3: Server-side detection (SSPP)

Server-side pollution rarely reflects anything, so detection leans on side effects. Send a polluted property and watch for changed behaviour:

  • Status code override: pollute a property that the framework reads for the response status, and watch for an unexpected code.

  • JSON spaces override: pollute the JSON serialiser’s spacing option and look for extra whitespace in the response body.

  • Charset or content-type override: pollute the response charset and watch the header change.

The Server-Side Prototype Pollution Scanner runs these techniques across proxied traffic and reports sources it finds.

Phase 4: Bypass input filters

Where the application strips __proto__, reach the prototype another way:

constructor[prototype][x]=y      # via constructor instead of __proto__
__pro__proto__to__[x]=y          # nesting that survives a single-pass strip

Obfuscation that survives the filter, then resolves to a prototype write, is the general move.

Phase 5: Server-side escalation

With a confirmed source, look for a gadget in the Node application or its dependencies. The high-value cases pollute a property that feeds child-process spawning options (an shell, NODE_OPTIONS, or argument array), reaching remote code execution. Lesser gadgets reach SQL injection, authentication bypass, or information disclosure. Confirm RCE out of band.

Output

  • The pollution source (parameter, merge, or parser) and whether it is client or server side.

  • The gadget reached, and the resulting impact (DOM XSS, RCE, auth bypass).

  • Any filter bypass needed to reach the prototype.

Techniques

Counter moves

Runbook: Prototype pollution is the variant in play. Freezing the prototype, validating JSON against a schema, and avoiding unsafe recursive merges are the counters. The defender’s view is in the blue notes on the application layer as a target.