# Description The challenge presents an HTML form asking you to enter your name. When you submit, it redirects to `/challenge?text=<USER_INPUT>`, which triggers some JavaScript to show a modal “Welcome, <USER_INPUT>!” on the screen. However, the code includes a naive **XSS filter** that attempts to prevent `<` or `>` from appearing in `location.search` or `location.hash` after a single `decodeURIComponent()`. ### Goal - **Trigger an XSS payload** despite this naive filter. - **Bypass** the check that blocks any decoded `<`/`>` in `window.location.search` or `window.location.hash`. --- # Challenge Analysis Below is the **relevant code**. We focus on the HTML, the `XSS()` check, and the parameter-handling logic: ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Welcome 2025!</title> <!-- (Styling omitted for brevity) --> </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> <!-- Modal --> <div id="modal" class="modal"> <div class="modal-content"> <h2 id="modalText"></h2> <button onclick="closeModal()">Close</button> </div> </div> <!-- Some styling and animations omitted --> <script> // --------------------------------------------------------------------- // 1. Naive XSS check // --------------------------------------------------------------------- function XSS() { // decodeURIComponent() is called ONCE for location.search and location.hash. // If we see any < or >, we flag it as XSS. return decodeURIComponent(window.location.search).includes('<') || decodeURIComponent(window.location.search).includes('>') || decodeURIComponent(window.location.hash).includes('<') || decodeURIComponent(window.location.hash).includes('>'); } // --------------------------------------------------------------------- // 2. Get parameter by name (custom regex-based approach) // --------------------------------------------------------------------- function getParameterByName(name) { var url = window.location.href; name = name.replace(/[\[\]]/g, "\\$&"); var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"); var results = regex.exec(url); if (!results) return null; if (!results[2]) return ''; return decodeURIComponent(results[2].replace(/\+/g, " ")); } // --------------------------------------------------------------------- // 3. On form submit, redirect to /challenge?text=<encoded input> // --------------------------------------------------------------------- function redirectToText(event) { event.preventDefault(); const inputBox = document.getElementById('inputBox'); const text = encodeURIComponent(inputBox.value); window.location.href = `/challenge?text=${text}`; } // --------------------------------------------------------------------- // 4. If 'text' exists and XSS() is false, show the modal // --------------------------------------------------------------------- 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 closeModal() { location.replace('/challenge'); } window.onload = function() { checkQueryParam(); }; </script> </body> </html ``` ## 2.1 The Naive Filter The `XSS()` function decodes `window.location.search` **once**, checks for `<` or `>`, and similarly checks the hash. If `<` or `>` is found, it halts. Otherwise, the code does: ```jsx modalText.innerHTML = `Welcome, ${text}!`; ``` ## 2.2 The Regex Parameter Extraction ```jsx function getParameterByName(name) { // ... var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"); // ... return decodeURIComponent(results[2].replace(/\+/g, " ")); } ``` - This looks for `?text=...` or `&text=...` **anywhere** in `window.location.href` (not strictly in the “official” query portion). - This mismatch between how the **filter** reads `location.search` and how the **regex** looks in `href` is the key vulnerability. --- # Solution ## 3.1 Bypassing the Filter in the Path To exploit the mismatch, we can place `&text=...` **within the path** so that: - **Browser** sees no official `?` → `window.location.search` is empty or does not contain `<`/`>`. - The code’s naive check `XSS()` sees no `<`/`>` in `search` or `hash`. - Meanwhile, `getParameterByName("text")` still detects `&text=...` from the full `href`, yielding a malicious string with real `<` / `>` once decoded. ### 3.1.1 Example Payload URL A typical exploit might look like: ``` https://challenge-0125.intigriti.io/&text=%3Cimg%20src%20onerror=alert(document.domain)%3E/..%2fchallenge ``` Breaking it down: 1. **No `?`** after the host. Instead, we go straight to `/&text=...`. 2. `%3Cimg%20src%20onerror=alert(document.domain)%3E` decodes to `<img src onerror=alert(document.domain)>`. 3. `/..%2fchallenge` is a path traversal trick that ensures the final **path** the browser resolves is **`/challenge`** - This is crucial because we need to *land* on `/challenge` (so the front-end code from `/challenge` loads and runs). - But we also need the `&text=payload` to stay in the final `window.location.href` so that the custom regex sees `&text=...`. - The result is that the browser’s path eventually is `/challenge`, the script from the page is loaded, and `location.search` is effectively empty or harmless, but `location.href` still contains `&text=...`. ### 3.1.2 What Happens Under the Hood - `window.location.search` → `""`. - `XSS()` → sees `decodeURIComponent("")`, no `<`/`>` → returns **false** (“no XSS here”). - `getParameterByName("text")` uses the regex on the entire `window.location.href`, finds `&text=%3Cimg%20...%3E`. - Decodes `%3C` as `<`, `%3E` as `>`. - The code then sets:This is valid HTML containing a real `<img>` with an `onerror` handler → triggers `alert(document.domain)`. ```jsx modalText.innerHTML = `Welcome, <img src onerror=alert(document.domain)>!`; ``` --- ## 3.2 Key Observations 1. **Landing on `/challenge`:** - The code in the HTML (especially the function `checkQueryParam()`) only runs if the user’s final URL is indeed pointing to the `/challenge` page (or includes it in some form so the browser loads that HTML/JS). - The string `/..%2fchallenge` manipulates the path so that after the malicious `&text=`, the browser still serves or lands on the correct route. - This ensures the correct JavaScript is loaded and executed. 2. **Avoiding `<` or `>` in `search`:** - If `<` / `>` appeared literally in `window.location.search` after decoding, the `XSS()` check would block us. - By encoding them (`%3C` / `%3E`) and ensuring the browser never interprets them as part of the official query, we slip past the filter. 3. **Regex vs. Browser Query Parsing**: - The browser’s query parsing expects a `?` to start parameters. - The custom regex looks for `[?&]text=...` anywhere in the **full** `href`. - This discrepancy is the root cause that allows the bypass. --- # Conclusion ### Summary - **Challenge**: The site tries to block `<` / `>` in `location.search` or `location.hash`. - **Exploit**: Place `&text=payload` in the **path** (e.g., after a slash) so that `location.search` does not contain `<`/`>`. - **Ensure** you end up on `/challenge` so that the correct page code is loaded. Hence using `/..%2fchallenge` after your payload in the path. - **Regex** in `getParameterByName("text")` picks up the parameter from the full URL, decodes `<img src onerror=…>`, and inserts it into `innerHTML`. - **Result**: The `onerror` attribute triggers JavaScript execution, leading to an XSS popup (`alert(document.domain)`).