Common prototype air pollution units | PortSwigger Analysis

Showing robots with browser logos on to represent gadgets

We recently launched a new version of DOM Invader that can find Client-Side Prototype Pollution (CSPP).

If you’re not already familiar with Client-Side Prototype Pollution, check out the post above. Just to recap, a successful CSPP exploit requires two components:

  • A way to poison the prototype, referred to as a prototype pollution source.
  • A way to use a poisoned prototype for an actual exploit, referred to as a prototype pollution gadget.

In this post we’re going to talk about CSPP gadgets in browser APIs and how we found them in common libraries too.

Prototype pollution gadgets in browser JavaScript APIs

I was quite surprised to discover that some JavaScript APIs in the browser contain prototype pollution gadgets. Functions that accept objects as arguments can be polluted just like any other object. The fetch function is one such example. When calling fetch, there is an optional argument that accepts an object – this allows you to control headers, body parameters, etc. If a site doesn’t specify one of those properties, then it’s possible to use prototype pollution to control them provided there is a prototype pollution source:

Object.prototype.body = "foo=bar";
fetch("", {method:"POST"})

Even ES5 functions such as Object.defineProperty are vulnerable – if a developer does not specify a “value” property, then prototype pollution sources can be used to overwrite properties! Consider the following example:

let myObject = {property:"Existing property value"};
Object.defineProperty(myObject,'property', {configurable:false,writable:false} ); = 'Should fail';
alert(;//Existing property value

If you use prototype pollution on the value property, then you can overwrite “property” even though it’s been configured as not writable:

let myObject = {property: "Existing property value"};
Object.defineProperty(myObject,'property', {configurable:false,writable:false});

So even though the property has been made unconfigurable and unwritable, by using a prototype pollution source we can poison the descriptor used by Object.defineProperty to overwrite the property value. This is because if you don’t specify a “value” property on the descriptor then the JavaScript engine uses the Object.prototype.

Google analytics

While testing various bug bounty sites, DOM Invader was reporting a gadget called “hitCallback“that was sent to a”setTimeout” sink. We traced this back to Google Analytics using the setTimeout sink, and discovered that “c” contains the value from the prototype pollution gadget:

this.Ja = function() {
   !b.fb && c && setTimeout(c, 10)

DOM Invader shows the “hitCallback“gadget inside the”setTimeout” sink:

A screenshot showing DOM Invader's augmented DOM view with setTimeout sink being displayed

So for any website that uses this version of Google Analytics, and has a client-side prototype pollution source, it would be possible to use this gadget to gain DOM XSS on the target website. We successfully exploited this gadget on a well known game site, and others.

Google tag manager

We’ve seen many websites, using Google tag manager, have a resultant vulnerable gadget “sequence“that ends up in an eval sink. There is also another gadget called “event_callback“which ends up in a”setTimeout” sink. We reported these to Google but they claim it’s the customer’s responsibility to ship code that doesn’t contain prototype pollution sources. Personally, I think they should also fix gadgets where possible as a defense in depth measure. Quite hilariously, our own Web Security Academy site has one of these gadgets but thankfully no prototype pollution source.

The Wordpress exploit looked like this:

The trailing “-” is required because the value ends up in a JavaScript expression alongside an integer.

A screenshot showing DOM Invader's augmented DOM view with an eval sink being displayed

Adobe dynamic tag management

Sergey Bobrov did an excellent job of documenting various CSPP vulnerabilities. On the Github page, I noticed Adobe’s dynamic tag management scripts and decided to scan them for gadgets. DOM Invader found multiple undocumented gadgets, as well as the existing documented ones. The gimmick”cspNonce“was being used in an innerHTML sink, as well as “bodyHiddenStyle“, this hit an innerHTML sink too but has an existing