Home Blog Intigriti 0522 — XSS Challenge Writeup (XSS through prototype pollution)

Intigriti 0522 — XSS Challenge Writeup (XSS through prototype pollution)

Intigriti 0522 — XSS Challenge Writeup (XSS through prototype pollution)

Introduction

In this blog post, I am going to walk through Intigriti’s May 2022 XSS challenge. I will explain how prototype pollution can be used to bypass a client-side HTML sanitizer to get Cross-site Scripting (XSS). The goal of this challenge is to pop an XSS alert that executes alert(document.domain). This was an easier challenge than the previous ones, but it was definitely interesting and I enjoyed solving it.

Website Overview

When we visit the main challenge page, we are greeted with a simple single page application. There is a navigation bar to visit other pages.

Hmm that background image looks interesting...
Hmm that background image looks interesting...

The text on the homepage and the background image really stand out here. Maybe this is hinting to prototype pollution? Let's find out by analyzing the source code.

Sourcecode Analysis

By looking at the Javascript code we can see that there is an object called pages. An object is a collection of pairs that contain keys and their corresponding values. Inside the object there a set of properties with HTML inside it.

var pages = {
    1: `HOME
      <h5>Pollution is consuming the world. It's killing all the plants and ruining nature, but we won't let that happen! Our products will help you save the planet and yourself by purifying air naturally.</h5>`,
    2: `PRODUCTS
      <br>
    <footer>
        <img src="https://miro.medium.com/max/1000/1*Cd9sLiby5ibLJAkixjCidw.jpeg" width="150" height="200" alt="Snake Plant"></img><span>Snake Plant</span>
      </footer>
      <footer>
        <img src="https://miro.medium.com/max/1000/1*wlzwrBXYoDDkaAag_CT-AA.jpeg" width="150" height="200" alt="Areca Palm"></img><span>Areca Palm</span>
      </footer>
    <footer>
        <img src="https://miro.medium.com/max/1000/1*qn_6G8NV4xg_J0luFbY47w.jpeg" width="150" height="200" alt="Rubber Plant"></img><span>Rubber Plant</span>
        </footer>`,
    3: `CONTACT
      <br><br>
      <b>
        <a href="https://www.facebook.com/intigriticom/"><img src="https://cdn-icons-png.flaticon.com/512/124/124010.png" width="50" height="50" alt="Facebook"></img></a>
        <a href="https://www.linkedin.com/company/intigriti/"><img src="https://cdn-icons-png.flaticon.com/512/61/61109.png" width="50" height="50" alt="LinkedIn"></img></a>
        <a href="https://twitter.com/intigriti"><img src="https://cdn-icons-png.flaticon.com/512/124/124021.png" width="50" height="50" alt="Twitter"></img></a>
        <a href="https://www.instagram.com/hackwithintigriti/"><img src="https://cdn-icons-png.flaticon.com/512/174/174855.png" width="50" height="50" alt="Instagram"></img></a>
      </b>
      `,
    4: `
      <div class="dropdown">
        <div id="myDropdown" class="dropdown-content">
          <a href = "?page=1">Home</a>
          <a href = "?page=2">Products</a>
          <a href = "?page=3">Contact</a>
        </div>
      </div>`
  };

Looking further we can see that it gets the value of the page by the page parameter. If the page value is in the pages object it will show the HTML content on the site. Otherwise it will redirect to the homepage. Another thing that sticks out is the filterXSS function. It filters the HTML content before outputting it on the website.

var pl = $.query.get('page'); // Get the page value from the query string
 if(pages[pl] != undefined){ // If the page value known, show the content on the site
  console.log(pages);
  document.getElementById("root").innerHTML = pages['4']+filterXSS(pages[pl]);
} else { // If the page value is unknown redirect to page 1 (Home)
  document.location.search = "?page=1"
}

Maybe there is a way to set our own attribute inside the pages object? There are 3 external Javascript scripts used on the website. These are for jQuery, xss (HTML Sanitizer) and a jQuery plugin called query-object. I looked around a bit and found out that the jQuery query-object plugin is indeed vulnerable to prototype pollution.

Source: https://github.com/BlackFan/client-side-prototype-pollution/blob/master/pp/jquery-query-object.md

Prototype Pollution

But what is prototype pollution and how can we use it to set our own attribute to the pages object?

Prototype pollution is a flaw that specifically affects Javascript applications. Every object in JavaScript is simply a collection of key and value pairs and that every object inherits from the Object type. An interesting thing to note is that all JavaScript objects share a prototype. So if we can pollute the Object type, each JavaScript object will be polluted.

I found this very useful GitHub repository called client-side-prototype-pollution. This repo is a collection of libraries which are vulnerable to prototype pollution. It also shows examples of useful script gadgets that can be used to demonstrate the impact.

The PoC (Proof of Concept) for the jQuery query-object prototype pollution is to use the following request parameter: ?__proto__[test]=test

By using __proto__ we can set our own attribute and a value for it. We can use this to inject HTML into to webpage.

Injecting HTML with prototype pollution
Injecting HTML with prototype pollution

Bypassing HTML sanitizer

Unfortunately we can't get XSS because it is filtered out by the xss module. But luckilly we can use prototype pollution to bypass it. The xss module accepts a second parameter, called options. It has a prototype called whiteList which can be polluted. Basicly the idea is to define our own whitelist accepting img tag with onerror and src attributes. This was identified by Michał Bentkowski, a security researcher. Make sure to read his blog post about bypassing client-side HTML sanitizers.

The GitHub repository already has a PoC for this that we can use to get XSS:

?__proto__[whiteList][img][0]=onerror&__proto__[whiteList][img][1]=src

Once we have defined the whitelist we can use the XSS payload: <img src=x onerror=alert(document.domain)>

Using this we can create the final URL (Don't forget to URL encode the XSS payload): Final URL

When visiting the link you can see that we have successfully executed the XSS popup! 🥳

I hope you enjoyed reading this write-up and learned something new! If you have feedback or any questions about this write-up, you can contact via the contact form or on Twitter.