Intigriti's January Challenge Writeup

Last updated on

Description

This challenge is a XSS challenge where we have to find xss on a webpage with a unique filtering.

Inital Look

Challenge website looks like a regular xss challenge where we have to enter a name and it will be shown in a pop up. Popup

Source Code

here is a stripped down version of the code


<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Welcome 2025!</title>
</head>

<body>
    <form id="textForm" onsubmit="redirectToText(event)">
        <h1>Enter your name!</h1>
        <label for="inputBox"></label>
        <input type="text" id="inputBox" name="inputBox" placeholder="Type here...">
        <button type="submit">Submit</button>
    </form>

    <div id="modal" class="modal">
        <div class="modal-content">
            <h2 id="modalText"></h2>
            <button onclick="closeModal()">Close</button>
        </div>
    </div>

    <div id="particles">
    </div>

    <script>
        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 getRandomColor() {
            // just for design 
        }

        function generateFallingParticles() {
            // just for design 
        }

        window.onload = function () {
            generateFallingParticles();
            checkQueryParam();
        };
    </script>
</body>

</html>

from our first look here our input is getting reflected in innerHTML only if 2 conditions are met

  • output of getParameterByName('text') isn’t empty
  • output of XSS() function should be false

Let’s address the elephant in the room: The JavaScript function XSS checks for the presence of < or > in window.location.search or window.location.hash. If it detects any angular brackets, it prevents our popup from loading. As of the time I’m writing this blog, it seems impossible to bypass this function—we can’t find a way to include angular brackets in location.hash or location.search.

Initially, I thought, “Hmm, maybe ISO-2022-JP is popular these days, and there might be a character that could replace angular brackets.” But after fuzzing, I couldn’t find any. I then realized how naive I was—if there were any replacement characters for angular brackets, Gareth Hayes would have discovered them long before I did.

But the application uses only UTF-8 it enforces that with the following tag in header <meta charset="UTF-8">

Let’s examine the other condition—how getParameterByName('text') is parsing user input. It doesn’t use a standard method to parse the query string, making this function quite suspicious. Let’s investigate further.

    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, " "));
    }

Above function is simplifiled below the regex here is searching for first ?text=data or &text=data.

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

Here, we have an advantage: we’re working with the entire URL, while the XSS function only checks for angular brackets in the hash and search sections. Our goal is to craft a payload that avoids both the hash and search parts. Time to open our trusty devtools!

parts in URL

As you can see we can use password, pathname, port, username sections of URL to hide our payload. My intial thought is to hide payload in username and password section and i tried the following. alt text This even satisfies our regex. alt text

issue here is when we use window.location.href username and password won’t be returned. As you can see my dev tools title i have visited https://test:pass@example.com but window.location.href is just https://example.com/ .

Username and password in url can&#x27;t be acceded using window api The only viable option left is the pathname. If we can craft a path that contains a valid regex payload while still pointing to the challenge route, we can achieve XSS. This opens the door to path traversal—replacing randomroute with our regex payload. However, the browser will simply step back in the directory structure and load the intended resource.

alt text

if we urlencode one / we can achive what we want.

alt text

The link https://challenge-0125.intigriti.io/randomroute/..%2fchallenge loads the challenge page instead of returning a 404. This means we can replace randomroute with c&text=<img src=x onerror=alert(document.domain)>. alt text

XSS link

https://challenge-0125.intigriti.io/c&text=<img src=x onerror=alert(document.domain)>/..%2fchallenge

alt text

Conclusion

The vulnerability highlighted here is called “parser differential” it typically refers to differences or inconsistencies in how various components or systems interpret the same input. here the URL input is parsed by 2 functions differently such that a inconsistencies occured. this is a pretty fun challenge by 0xGodson and kudos to intigriti for hosting such a good challenge.