
A Deep Dive into Client-Side Prototype Pollution
Posted on September 16, 2025 by Jack Mason
What is Prototype Pollution?
Prototype Pollution is a subtle yet potent JavaScript vulnerability. To understand it, think of JavaScript objects as being built from a master blueprint, known as the Object.prototype. This prototype is the master template from which almost all objects inherit their basic methods and properties, like .toString() or .hasOwnProperty().
The vulnerability occurs when an attacker finds a way to modify, or “pollute,” this shared blueprint. By injecting malicious properties into the Object.prototype, an attacker can affect the behaviour of every object created thereafter, causing widespread and unpredictable issues. This flaw typically arises when an application’s code unsafely merges or copies data from user input without properly sanitising it first.
The impact of this attack depends heavily on where the code is running. On the server-side (e.g., in a Node.js application), the vulnerability is critical, potentially leading to Remote Code Execution (RCE). On the client-side (in a user’s browser), the consequences are usually limited to that single user, ranging from crashing their browser tab (Denial of Service) to executing a Cross-Site Scripting (XSS) attack.
This blog post will focus on the dangers and remediation of client-side prototype pollution.
Understanding Client-Side Prototype Pollution
At its core, client-side prototype pollution is caused by a single fundamental misconfiguration: the unsafe recursive merging of JavaScript objects. This happens when a web application takes user-controllable input and copies its properties into an existing object without validation.
The most common vulnerable scenarios involve:
1. Parsing URL Parameters:
A frequent source is the query string or the URL fragment (the part after the #). JavaScript on the page might parse these values to configure the application. An attacker can craft a URL like https://example.com/?__proto__[isAdmin]=true to inject properties.
2. Processing JSON Input:
Web applications often receive data in JSON format, for example, from postMessage events. If this JSON is parsed and merged into an application object without due care, it can lead to pollution.
3. Vulnerable Third-Party Libraries:
The unsafe merging logic is often found within popular third-party libraries used for deep-copying objects or setting nested properties. Using a vulnerable version of a library like Lodash or jQuery can make an application susceptible.
The key to the exploit is the special __proto__ property. When a vulnerable script tries to set a property on __proto__, it doesn’t modify the local object. Instead, it traverses up to the master blueprint and modifies the global Object.prototype, affecting all objects application-wide.
How To Detect Client-Side Prototype Pollution
The most common way for a security researcher or user to detect this vulnerability is by manipulating the website’s URL. The goal is to inject a property into the Object.prototype and then check if it’s present.
Here is a basic, step-by-step guide:
1. Craft the URL:
Take the URL of the page you want to test and append a test payload to it: ?__proto__[jack]=mason. For example, if you are on https://example-website.co.uk, change the URL in your address bar to:
https://example-website.co.uk?__proto__[jack]=mason
Then, press Enter to load the page.
2. Open the Developer Console:
Once the page has reloaded, open your browser’s developer tools (usually by pressing F12) and navigate to the “Console” tab.
3. Check the Prototype:
In the console, type the following command and press Enter:
Object.prototype.jack
If the website is vulnerable, the console will return the string "mason". This confirms that the script has incorrectly processed the URL, allowing you to “pollute” the master Object.prototype.
To see the global impact, you can create a brand new, empty object and see if it inherits your property:
let newObject = {};
console.log(newObject.jack); // Will also output "mason"
Impact of Client-Side Prototype Pollution
Once the prototype is polluted, every object is likely to have the property jack. While this seems harmless, an attacker can use this ability to target properties that the application uses for sensitive operations, leading to serious security flaws. The most prevalent outcome is DOM-based Cross-Site Scripting (XSS).
What is DOM-Based XSS?
DOM-based XSS occurs when a web application’s client-side script writes user-provided data to the Document Object Model (DOM) without proper sanitisation. Unlike other XSS types, the entire attack happens in the browser. The server is not involved; the malicious payload is executed by a legitimate script already on the page that has been tricked into processing it.
If a user can control the property of an object through prototype pollution, they may be able to pass a malicious payload to a “sink”—a function or property that renders HTML, such as .innerHTML.
Take the following vulnerable code snippet as an example. It intends to take a configuration object and render some custom HTML into a banner:
// A script somewhere in the application that renders a banner
function renderBanner(config) {
const banner = document.getElementById('welcome-banner');
// A developer expects config.customHTML to be set safely
// If config.customHTML is not defined, it defaults to a welcome message
banner.innerHTML = config.customHTML || 'Welcome to our website!';
}
// Let's assume 'appConfig' is an object populated from URL parameters
// by a vulnerable merging function.
renderBanner(appConfig);
This code becomes exploitable through prototype pollution. The attacker can’t control appConfig directly, but they can pollute the Object.prototype. When the renderBanner function checks for appConfig.customHTML, it won’t find it. JavaScript will then look up the prototype chain and find the attacker’s polluted property instead.
This can be exploited with the following payload in the URL:
?__proto__[customHTML]=<img src=x onerror=alert(document.domain)>
When the vulnerable script parses this URL, it pollutes Object.prototype.customHTML. Later, when renderBanner is called, appConfig.customHTML resolves to the malicious payload, which is then written to the page’s DOM via .innerHTML, executing the script.
Remediating Client-Side Prototype Pollution
Protecting against this vulnerability requires a multi-layered approach from developers.
1. Sanitise User Input:
The most direct defence is to validate and sanitise all user-provided input, especially when it is used to define object properties. A common practice is to disallow or strip out keys named __proto__, constructor, and prototype.
2. Use Prototype-less Objects:
For objects that are used as key-value maps or dictionaries, create them using Object.create(null). This creates a clean object that does not inherit from Object.prototype, so there is no prototype to pollute.
let safeMap = Object.create(null);
// This object has no __proto__ property to exploit.
3. Freeze the Prototype:
A more robust, global solution is to make the master blueprint immutable with Object.freeze(Object.prototype). This prevents any modifications to Object.prototype for the entire application. Be cautious, as some third-party libraries may not function correctly if the prototype is frozen.
4. Use Maps Instead of Objects:
For dictionary-like data structures, prefer using JavaScript’s Map type over plain objects (). Maps are designed for this purpose and are not susceptible to prototype pollution.
5. Keep Dependencies Updated:
Many prototype pollution vulnerabilities are discovered in third-party libraries. Regularly audit and update your project’s dependencies using tools like npm audit to patch known vulnerabilities.