For the complete documentation index, see llms.txt. This page is also available as Markdown.

Challenge 0526 Write-up

A detailed write-up for Intigriti's May Challenge involving XSS via DOM Clobbering and Insufficient Content Filtering.

Enumeration

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

Landing Page

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

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

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

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.

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.

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.

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

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.

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:

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.

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

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.

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.

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

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.

Last updated