Skip to content
Field Detail
Platform PortSwigger Web Security Academy
Type Prototype Pollution / DOM XSS
Difficulty Practitioner
Objective Bypass a single-pass keyword sanitization on the URL parameter key to pollute Object.prototype via transport_url and trigger alert()

Client-Side Prototype Pollution via Flawed Sanitization

Both __proto__[x] and __proto__.x notation returned undefined — sanitization was stripping the traversal keyword. Checking searchLoggerFiltered.js in DevTools:

function sanitizeKey(key) {
    let badProperties = ['constructor','__proto__','prototype'];
    for(let badProperty of badProperties) {
        key = key.replaceAll(badProperty, '');
    }
    return key;
}

Single-pass replaceAll on each keyword — that's the flaw. replaceAll removes every occurrence of the substring from the string as it exists at the time of the call; it doesn't re-scan the output. If the blocked keyword is embedded inside itself, the outer characters survive after the inner match is removed:

__pro__proto__to__
           ↓ replaceAll('__proto__', '')
__pro       to__   →   __proto__

The sanitizer finds __proto__ starting mid-string and removes it, leaving __pro + to__ = __proto__. The reconstructed string is never scanned again. The fix is to loop until the output stabilizes — replace repeatedly until no more matches are found. A blocklist that needs to anticipate every obfuscation variant is inherently fragile; an allowlist permitting only alphanumeric key characters doesn't need to know the attacks in advance.

Testing the bypass with a benign property:

/?__pro__proto__to__[tetoProperty]=miku
Screenshot
Object.prototype
// Object { tetoProperty: "miku", … }

Pollution confirmed. The gadget is the same as the unpatched lab — transport_url flows to script.src with no own-property definition on config. Once the sanitization bypass is done, the rest is unchanged:

/?__pro__proto__to__[transport_url]=data:,alert(1)
Screenshot

Alert fired.

Lab solved :P

Resources