LOADING
PREYANSH
SHAH
WRITING
001
ARTICLE
MARCH 16, 2026 PREYANSH SHAH

Polluting JavaScript Prototypes Until the Whole App Broke (In My Favor)

Prototype pollution in a client-side library that cascaded into DOM-based XSS. One crafted URL parameter. Every user who visited the page. JavaScript runs.

002

JavaScript is a deeply strange language.

One of its stranger features: every object inherits from Object.prototype. Every single one. Which means if you can modify Object.prototype, you’ve modified the base behavior of every object in the application.

You’ve broken physics. Congratulations.

This is prototype pollution. And it’s what happens when a developer writes obj[key] = value with a user-controlled key and doesn’t account for the possibility that key is __proto__.


The Target and the Library

Redacted-dashboard.com — a data visualization platform. Client-heavy, lots of JavaScript, complex frontend that processed URL parameters to render dashboard states.

The library doing the heavy lifting was a custom query string parser — not a major open-source library, but an in-house one — that took URL parameters and merged them into an application state object.

The merge logic, roughly:

function merge(target, source) {
  for (let key in source) {
    target[key] = source[key];
  }
}

Simple recursive merge. key comes from URL parameters. source comes from parsing the URL.

If key is __proto__ and source[key] is {"isAdmin": true}:

target["__proto__"] = {"isAdmin": true}

Which is equivalent to:

Object.prototype.isAdmin = true;

Every object in the application now has isAdmin: true. Including every object that checks if (user.isAdmin).


Finding and Exploiting It

I fed the URL parser a crafted query string:

?__proto__[polluted]=yes

Then in the browser console:

console.log({}.polluted) // "yes"

The empty object had the property. Prototype pollution confirmed.

Now: pollution alone isn’t useful unless something in the application uses polluted properties in a dangerous way. I went hunting.

Found a template rendering function that used innerHTML with a property that fell back to Object.prototype if not explicitly set on the template object:

element.innerHTML = template.content || '';

If template.content was undefined, it checked the prototype chain. If I’d polluted Object.prototype.content with an XSS payload:

?__proto__[content]=<img src=x onerror=alert(document.domain)>

Every template object without an explicit content property would render my payload via innerHTML.

The alert fired. DOM-based XSS via prototype pollution. Triggered by a URL parameter. Shareable via a link.


The Report

Title: Prototype Pollution in URL Query String Parser Leads to DOM-Based XSS via Polluted Template Property

Severity: High

Impact: An attacker can craft a URL containing __proto__ parameters that pollute Object.prototype for any user visiting the link. This leads to DOM-based XSS via a template renderer that inherits polluted properties, enabling JavaScript execution in victim sessions without user interaction beyond clicking a link.

Fix:

  • In the merge function: check if (key === '__proto__' || key === 'constructor' || key === 'prototype') continue;
  • Or use Object.create(null) for merge targets (objects with no prototype chain)
  • Use Object.hasOwnProperty checks before using inherited properties in security-relevant contexts
  • Replace innerHTML with textContent where HTML rendering is not required

Patched in 48 hours. Bounty: $$$


The Lesson

Prototype pollution is a JavaScript-specific vulnerability that didn’t get serious attention until around 2019. It’s still underappreciated in bug bounty because it requires knowing: what happens after pollution? Pollution alone is useless. The value is in the gadget — the downstream code that uses polluted properties dangerously.

Hunting prototype pollution means finding the pollution source and then hunting through the application’s JavaScript for gadgets. It’s more research-intensive than most client-side bugs. Which means fewer hunters are looking for it. Which means it’s still found in production applications regularly.

Worth adding to your toolkit.


Reported through the official bug bounty program. Domain redacted per responsible disclosure norms.

003
END
← BACK TO WRITING
Polluting JavaScript Prototypes Until the Whole App Broke (In My Favor) READY TO PLAY