Post

Intigriti Monthly Challenge 0125 by Godson

Intigriti Monthly Challenge 0125 by Godson

Challenge 0125 by Godson

Description

This challenge is part of Intigriti’s monthly challenges. The point of the challenge is to trigger XSS on the given domain with no user interaction required, and the solution must work on the latest version of both Chrome and Firefox.

https://challenge-0125.intigriti.io/challenge

Solution

First Look

The application takes user input via a form and reflects it in a modal after URL redirection.

alt text

alt text

Custom XSS sanitization functions

By taking a look at the HTML source code we find the following custom JS functions that are used to sanitize our input.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
function XSS() {
    return decodeURIComponent(window.location.search).includes('<') || decodeURIComponent(window.location.search).includes('>') || decodeURIComponent(window.location.hash).includes('<') || decodeURIComponent(window.location.hash).includes('>')
}
function getParameterByName(name) {
    var url = window.location.href;
    name = name.replace(/[\[\]]/g, "\\$&");
    var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)");
    results = regex.exec(url);
    if (!results) return null;
    if (!results[2]) return '';
    return decodeURIComponent(results[2].replace(/\+/g, " "));
}

// Function to redirect on form submit
function redirectToText(event) {
    event.preventDefault();
    const inputBox = document.getElementById('inputBox');
    const text = encodeURIComponent(inputBox.value);
    window.location.href = `/challenge?text=${text}`;
}

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

// Function to close the modal
function closeModal() {
    location.replace('/challenge')
}

// Function to generate random color
function getRandomColor() {
    const letters = '0123456789ABCDEF';
    let color = '#';
    for (let i = 0; i < 6; i++) {
        color += letters[Math.floor(Math.random() * 16)];
    }
    return color;
}

// Function to generate falling particles
function generateFallingParticles() {
    const particlesContainer = document.getElementById("particles");

    // Generate 100 particles with different animations, positions, and colors
    for (let i = 0; i < 100; i++) {
        let particle = document.createElement("div");
        particle.classList.add("falling-particle");

        // Randomize the particle's left position
        particle.style.left = Math.random() * 100 + "vw";  // Left position from 0 to 100% of viewport width

        // Randomize the particle's color
        particle.style.backgroundColor = getRandomColor();

        // Randomize animation delays
        particle.style.animationDelay = Math.random() * 5 + "s";  // Random delay for staggered fall
        particlesContainer.appendChild(particle);
    }
}

// Generate particles when the page loads
window.onload = function () {
    generateFallingParticles();
    checkQueryParam();
};

XSS() Function

Checks whether there is a ‘<’ or ‘>’ symbol in the search part of the URL or in the fragment part of the URL, if at least one of these characters is included then the function returns true, otherswise it returns false.

Calling the XSS() function for the following URLs would return:

getParameterByName() Function

Extracts a query parameter’s value from the current URL.

  1. Get URL: window.location.href
  2. Escape Special Chars: Handles [ and ] in the parameter name.
  3. Regex Match: Finds the parameter and its value in the URL.
  4. Handle Results:
    • Returns null if the parameter doesn’t exist.
    • Returns '' if the parameter has no value.
  5. Decode Value: Converts URL-encoded chars (e.g., %20 → space, + → space).

Example:

Notes:

  • Keep in mind that the function uses window.location.href instead of window.location.search.

checkQueryParam() Function

Checks for a text query parameter and displays a modal if valid.

  1. Get text Parameter: Uses getParameterByName('text') to extract the value.
  2. Validation:
    • Checks if text exists and XSS() returns false.
  3. Display Modal:
    • Updates the modal’s content with Welcome, ${text}!.

window.onload Function

Runs when the page finishes loading.

  1. Generate Falling Particles: Calls generateFallingParticles().
  2. Check Query Parameter: Calls checkQueryParam() to handle the text query parameter and display a modal if valid.

The Path

So far we know the following:

  1. We provide our input via the text URL param.
  2. XSS() checks if there are any illegal characters in our input but only takes into account window.location.search part.
  3. getParameterByName() extracts the value from text param but takes into account the whole URL window.location.href.

Do you see the problem?

There is a discrepancy between points 2 and 3. What will happen if we provide our input as part of the path?

URL Normalization and Payload Explanation

URL Normalization

URL normalization is the process of transforming a URL into a standardized format. This includes:

  1. Removing redundant path segments: For example, /challenge/../ becomes /.
  2. Decoding percent-encoded characters: For example, %2f becomes /.
  3. Resolving relative paths: For example, /challenge/foo/../bar becomes /challenge/bar.

In this challenge, the URL normalization process plays a key role in how the text parameter is interpreted and displayed.

Payload Breakdown

The payload: https://challenge-0125.intigriti.io/challenge/&text=mariosk1574/..%2f

  1. URL Structure:
    • The URL contains a text parameter with the value mariosk1574/..%2f.
    • The %2f is the URL-encoded representation of /.
  2. Normalization Process:
    • When the browser processes the URL, it normalizes the path:
      • challenge/&text=mariosk1574/..%2f becomes challenge/&text=mariosk1574/../.
    • The ../ is interpreted as a relative path, moving up one directory level.
  3. Resulting Path:
    • After normalization, the text parameter effectively becomes mariosk1574/../.
    • This is passed to the getParameterByName function, which extracts the value mariosk1574/../.
  4. Modal Display:
    • The checkQueryParam function sets the modal’s innerHTML to:
      1
      
      modalText.innerHTML = `Welcome, ${text}!`;
      
    • Since text is mariosk1574/../, the modal displays:
      1
      
      Welcome, mariosk1574/../!
      

Why This Works

  • URL Encoding: The %2f encoding allows the / character to be included in the text parameter without breaking the URL structure.
  • Path Traversal: The ../ sequence is normalized by the browser, but it doesn’t affect the server-side logic because the text parameter is processed client-side.
  • Client-Side Rendering: The innerHTML assignment in the modal directly uses the normalized value, allowing the payload to be displayed as-is.

alt text

Exploitation

Our goal is to inject a payload that bypasses the XSS() check and executes JavaScript in the context of the modal. Since the innerHTML assignment in the modal does not allow <script> tags, we use an <img> tag with a broken src to trigger the onerror event.

Final Payload:

1
<img src=X onerror="alert(document.domain)">

https://challenge-0125.intigriti.io/challenge/&text=%3Cimg%20src=X%20onerror=%22alert(document.domain)%22%3E/..%2f

alt text

This post is licensed under CC BY 4.0 by the author.