🧠 Understanding Prototype Pollution Exploitation
Prototype Pollution occurs when an application recursively merges user-controllable input into an object without sanitizing the keys. This allows an attacker to inject properties into the global Object.prototype, which are then inherited by all objects in the application.
This guide breaks down nine distinct exploitation scenarios found in the PortSwigger Web Security Academy, covering both Client-Side (DOM XSS) and Server-Side (Privilege Escalation & RCE) attacks.
🧪 LAB 1: Client-Side Prototype Pollution via Browser APIs
🧐 How the Vulnerability Exists
The application uses Object.defineProperty() to define a configuration property (transport_url), making it non-writable and non-configurable. However, the descriptor object passed to defineProperty fails to define a value.
Root Cause: When value is missing from the descriptor, the browser looks up the prototype chain. If Object.prototype is polluted with a value, defineProperty inherits and uses it.
🚨 Exploitation Steps
-
Detect Source: Inject a property via the URL query string:
/?__proto__[foo]=bar. CheckObject.prototype.fooin the console. If it exists, pollution is confirmed. -
Analyze Gadget: Review
searchLoggerConfigurable.js. It definestransport_urlbut omits thevaluein the descriptor. -
Exploit: Inject a malicious
valueproperty that contains the XSS payload. Payload:/?__proto__[value]=data:,alert(1);. -
Result: The application inherits the malicious value for
transport_urland creates a script tag pointing to it, executing the alert.
IMPACT: DOM XSS.
🧪 LAB 2: DOM XSS via Client-Side Prototype Pollution
🧐 How the Vulnerability Exists
The application parses the URL query string and recursively merges it into an object. It later checks a config object for a transport_url property. Since this property is undefined on config, it falls back to the prototype.
Root Cause: Insecure recursive merge function allowing __proto__ as a key.
🚨 Exploitation Steps
-
Enable DOM Invader: Use Burp’s browser. Enable DOM Invader and toggle “Prototype pollution” ON.
-
Scan: Reload the page. DOM Invader identifies the
searchquery string as a vector. Click Scan for gadgets.
-
Exploit: DOM Invader finds that
transport_urlflows into ascript.srcsink. Click Exploit. Manual Payload:/?__proto__[transport_url]=data:,alert(1);.
IMPACT: DOM XSS.
🧪 LAB 3: DOM XSS via an Alternative Prototype Pollution Vector
🧐 How the Vulnerability Exists
The application attempts to sanitize input by stripping the __proto__ key when used in bracket notation (e.g., a[__proto__]). However, it fails to block dot notation.
Root Cause: Inconsistent sanitization allows a.__proto__ to pass through.
🚨 Exploitation Steps
-
Test Bypass: Try
/?__proto__[foo]=bar-> Fails (Filtered). Try/?__proto__.foo=bar-> Success (Polluted). -
Identify Gadget: The code calls
eval(manager.sequence). Thesequenceproperty is undefined onmanager. -
Craft Exploit: Inject
sequencevia the alternative vector. Payload:/?__proto__.sequence=alert(1)-Note: The trailing-fixes the syntax error because the app appends data to the payload.
IMPACT: Arbitrary JavaScript Execution (DOM XSS).
🧪 LAB 4: Client-Side Prototype Pollution via Flawed Sanitization
🧐 How the Vulnerability Exists
The application uses a sanitization function that removes the string __proto__ but does not do so recursively.
Root Cause: Non-Recursive Stripping. If we nest the key (__pro__proto__to__), the sanitizer removes the middle __proto__, and the remaining characters collapse to form the dangerous key again.
🚨 Exploitation Steps
-
Bypass Filter: Inject:
/?__pro__proto__to__[foo]=bar. Check console:Object.prototype.fooexists. -
Exploit Gadget: The app uses
transport_urlto load scripts. Payload:/?__pro__proto__to__[transport_url]=data:,alert(1);.
IMPACT: DOM XSS via Filter Bypass.
🧪 LAB 5: Client-Side Prototype Pollution in Third-Party Libraries
🧐 How the Vulnerability Exists
The application uses an older version of a third-party library (like Google Analytics or a slider) that contains a known prototype pollution vulnerability. The vector is the URL fragment (location.hash).
Root Cause: Vulnerable library code parsing configuration from the URL hash.
🚨 Exploitation Steps
-
Scan with DOM Invader: Enable DOM Invader. Reload. It detects a vulnerability in the
hash.
-
Identify Gadget: The scan reveals the
hitCallbackgadget which flows intosetTimeout. -
Deliver Exploit: Use the Exploit Server to redirect the victim:
<script> location="[https://YOUR-LAB-ID.web-security-academy.net/#__proto](https://YOUR-LAB-ID.web-security-academy.net/#__proto)__[hitCallback]=alert(document.cookie)" </script>
IMPACT: DOM XSS via Third-Party Library.
🧪 LAB 6: Privilege Escalation via Server-Side Prototype Pollution
🧐 How the Vulnerability Exists
The Node.js backend merges JSON input from the “Update Address” feature into a user object. It does not filter __proto__.
Root Cause: Server-side recursive merge allowing modification of the global object prototype.
🚨 Exploitation Steps
-
Analyze Request: Intercept the
POST /my-account/change-addressrequest. Note the JSON structure. - Pollute:
Add a
__proto__object with anisAdminproperty."__proto__": { "isAdmin": true } -
Verify: The server response reflects
"isAdmin": true. Refresh the page to see the “Admin Panel” link.
IMPACT: Vertical Privilege Escalation.
🧪 LAB 7: Detecting SSPP Without Polluted Property Reflection (Blind)
🧐 How the Vulnerability Exists
The application is vulnerable to Server-Side Prototype Pollution (SSPP) but does not reflect the polluted properties in the response (Blind).
Root Cause: We must use Status Code Override to detect the pollution. Node’s default error handling looks for a status property on error objects.
🚨 Exploitation Steps
-
Establish Baseline: Send invalid JSON. Note the default error status (e.g., 400 or 500).
- Pollute Status:
Inject a payload to set the global status code.
"__proto__": { "status": 555 } -
Trigger Error: Send invalid JSON again. Observation: The response status is now 555, confirming pollution.

IMPACT: Confirmed Blind Prototype Pollution.
🧪 LAB 8: Bypassing Flawed Input Filters for Server-Side Prototype Pollution
🧐 How the Vulnerability Exists
The server sanitizes the __proto__ key but fails to block the constructor property.
Root Cause: Constructor Injection. myObj.constructor.prototype resolves to the same object as myObj.__proto__.
🚨 Exploitation Steps
-
Test Filter: Inject
__proto__with"json spaces": 10. Check Raw response. Indentation unchanged (Blocked). - Bypass:
Use the constructor vector.
"constructor": { "prototype": { "isAdmin": true } } -
Verify: The response reflects
isAdmin: true. Access the admin panel to solve.
IMPACT: Privilege Escalation via Filter Bypass.
🧪 LAB 9: Remote Code Execution via Server-Side Prototype Pollution
🧐 How the Vulnerability Exists
The application uses child_process.fork() or exec() to run background maintenance jobs. It is vulnerable to pollution.
Root Cause: Node’s child process functions accept an options object. If execArgv is undefined, it reads from the prototype. We can inject the --eval flag to run arbitrary code.
🚨 Exploitation Steps
-
Detect Pollution: Use
"constructor": {"prototype": {"json spaces": 10}}to confirm vulnerability via response indentation. - Inject RCE Payload:
Pollute
execArgvto execute a command when a child process spawns."__proto__": { "execArgv": [ "--eval=require('child_process').execSync('rm /home/carlos/morale.txt')" ] } -
Trigger: Click the “Run maintenance jobs” button in the admin panel. The child process spawns, inherits the malicious arguments, and executes the command.

IMPACT: Remote Code Execution (RCE).
⚡ Fast Triage Cheat Sheet
| Attack Vector | 🚩 Immediate Signal | 🔧 The Critical Move |
|---|---|---|
| Client Standard | ?__proto__[test]=1 |
Check Object.prototype.test in console. |
| Client Alternative | Standard blocked. | Try ?__proto__.test=1 (Dot notation). |
| Client Sanitization | __proto__ stripped. |
Try ?__pro__proto__to__[test]=1. |
| Server Reflected | Input mirrored in JSON. | Inject {"__proto__": {"isAdmin": true}}. |
| Server Blind | No reflection. | Inject {"__proto__": {"status": 555}} & trigger error. |
| Server Bypass | __proto__ blocked. |
Use {"constructor": {"prototype": ...}}. |
| Server RCE | Child processes used. | Pollute execArgv or shell/input. |