This write-up explains a Cross-Site Scripting vulnerability discovered in Intigriti Challenge 0526.

Intigriti Challenge 0526 Write-up: Stored XSS via Unsanitized Display Name

Summary

This write-up explains a Cross-Site Scripting vulnerability discovered in Intigriti Challenge 0526.

The application allows users to update their profile Display Name. This value is later rendered as the testimonial author name in the testimonials feed. While the testimonial content is sanitized with DOMPurify, the author display name is inserted directly into the DOM using innerHTML.

As a result, HTML saved as a user's display name is interpreted by the browser and can execute JavaScript when the testimonials feed is rendered.

Vulnerability

The vulnerability exists in the testimonial rendering logic. The testimonial content is sanitized, but the user name is not.

Relevant client-side code:

nameDiv.innerHTML = t.user_name;
textDiv.innerHTML = DOMPurify.sanitize(t.content);

The important difference is:

  • t.content is sanitized with DOMPurify.sanitize().
  • t.user_name is inserted directly with innerHTML.

Because t.user_name is user-controlled, this allows an attacker to store HTML in their profile display name and have it rendered as executable markup in the testimonials section.

Root Cause

The root cause is unsafe rendering of user-controlled data in an HTML context.

Unsafe implementation:

nameDiv.innerHTML = t.user_name;

This causes the browser to parse any HTML contained in t.user_name.

A safer implementation would be:

nameDiv.textContent = t.user_name;

If HTML rendering is intentionally required, the value should be sanitized before being assigned to innerHTML.

Payload

The following payload was saved as the profile display name:

<details open ontoggle=al&#x65rt`1`>

When the testimonials feed rendered, the payload was inserted into the DOM through innerHTML. In Chrome, this automatically triggered the toggle event and executed JavaScript.

Filter Bypass Explanation

The server-side SCA Shield blocks obvious XSS signatures and several commonly used characters, including:

  • Quotes
  • Parentheses
  • Dots
  • Commas
  • Semicolons
  • Obvious alert patterns

The payload avoids those restrictions:

<details open ontoggle=al&#x65rt`1`>

al&#x65rt

The payload does not contain the plain string alert.

al&#x65rt

After HTML entity decoding, the browser interprets it as:

alert

This helps bypass filters that look for the literal alert keyword.

Tagged Template Call

The payload does not use a classic function call such as:

alert(1)

Instead, it uses JavaScript tagged template syntax:

alert`1`

This allows alert to be called without parentheses.

Automatic Event Trigger

The payload uses the following HTML element:

<details open ontoggle=...>

When a details element with the open attribute is inserted into the DOM, Chrome can automatically fire the toggle event. This allows the JavaScript handler to execute without requiring user interaction.

Steps To Reproduce

  1. Open https://challenge-0526.intigriti.io/challenge.
  2. Register a new account or log in.
  3. Go to the Profile section.
  4. Set the Display Name to:
<details open ontoggle=al&#x65rt`1`>
  1. Click Update Name.
  2. Go to the Testimonials section.
  3. Submit any testimonial, for example:
test
  1. Visit:
https://challenge-0526.intigriti.io/challenge#testimonials
  1. When the testimonials feed renders, an alert is triggered.

Impact

This vulnerability allows an attacker to store a malicious HTML/JavaScript payload in their profile display name. When the testimonials feed is viewed, the payload executes in the context of the application.

Successful exploitation may allow an attacker to:

  • Execute JavaScript in another user's browser context
  • Manipulate page content
  • Monitor user interactions
  • Target sensitive in-app data
  • Send authenticated requests to same-origin endpoints

Although the proof of concept uses alert(1), the underlying issue could have a higher impact in a real-world application depending on available functionality, authentication state, and exposed same-origin endpoints.

Conclusion

The vulnerability was caused by inconsistent sanitization in the testimonial rendering logic.

While testimonial content was safely processed with DOMPurify, the testimonial author name was inserted directly into the DOM with innerHTML. This allowed a malicious profile display name to become executable markup.

The issue is simple at the code level but effective in practice: the payload is stored in the profile, rendered in the testimonials feed, and executed automatically when the vulnerable component is loaded.