Intigriti XSS Challenge 0221 Write-Up
XSS Challenge 0221
This month’s XSS Challenge was made by @Holme. The challenge can be found at https://challenge-0221.intigriti.io/.
The challenge is a simple one page website. There is a form that takes an assignment title, text and an answer to the anti-robot question.
After submission, a ‘share’ link is generated, which is simply the same URL with the assignment title and text as search parameters (?assignmentTitle=&assignmentText=
).
By taking a closer look at the only (interesting) included javascript file: script.js
, we see it contains some simple code that first calls a function that checks the length of the assignment text, if it’s more than 50 characters, a succesful message is stored in result
. Then in the original function, result
is set equal to window.result
if it’s not undefined, otherwise an error message is stored in result
. This is important, because if we can control window.result
and make the assignment text less than 50 characters, we can control result
after the check.
Another function is called, which retrieves the value of the answer input field. Then a function is called that checks the answer of the question by using eval()
. This should immediately catch your attention, as eval()
is a good place to look for XSS. To be more specific, it calls eval(result.questionAnswer.value + "==" + question)
where question
is a randomly generated question in the form of X + Y
. Also note that the last few lines will autosubmit the form if autosubmit
is a search parameter.
You might think that inputting alert(1)
in the answer input field will already trigger XSS, but this will not work, as the function that retrieves the value also checks it against a regular expression, namely /^[0-9]+$/
, which only allows for digits. Furthermore, getting the value in the input field would require user interaction, which is not allowed for this challenge.
However, the result.questionAnswer.value
variable is indeed the only variable we might be able to control so that it could get evaluated and cause XSS. For this we would have to control window.result
and this can be done using DOM clobbering. But to achieve DOM clobbering, we would have to find a way to inject some HTML.
We can start to input different values in the assignment title in the hope of breaking out of the input element. But all useful special characters are escaped. However, by trying %00
we can see something strange. It gets returned in the input field as �00
. This is strange, because %00
should only become �
without the extra two zero’s. And surely enough, %01
becomes �01
.
Now we can try to input ∼
which is \u223c
in the hope it will get converted to \x22\x3c
or ">
. The input will actually be returned as "3c
, which will allow us to break out of the input’s value attribute. Apparently the first unicode byte is taken as a one byte ASCII character, while the other byte follows as the literal hexadecimal characters.
If we try ∀㸀
or \u2200\u3e00
, it indeed becomes "00>00
and we succesfully break out of the input element. Inline script is disabled, so we can not just add <script>
or an event handler. The last step is to use DOM clobbering to control window.result
. This can simply be done by creating elements with an id attribute set to result
. This will overwrite the window.result
variable with the element, but remember that in the eval()
result.questionAnswer.value
is used, so we have to create those fields as well.
Another difficulty is that the decoded unicode is followed by two hexadecimal digits. So to create a new element, the opening bracket <
has to be followed by [0-9a-f]
twice. We can achieve the first level of depth with made up elements, so <ca id=result><ca id=result name=questionAnswer>
would result in result.questionAnswer
to be equal to the second element, however .value
would still be undefined. This works because multiple elements with the same id (result
) get put together in a DOM collection at window.result
and we can refer to them by name. Normally we can use <form>
and <input>
to achieve another level of depth, but fo
and in
are not all hexadecimal characters, so that would not work here.
We have to create a list of HTML elements that start with 2 hexadecimal characters and find one that has a value
field. The element we are looking for is <data>
, it normally has a value
attribute, which is indeed returned when checking it in the DOM.
By injecting <data id=result><data id=result name=questionAnswer value=alert(origin)>
, we can set result.questionAnswer.value
to alert(origin)
, which is directly passed into eval()
, resulting in XSS.
Together with the unicode characters, this creates the following payload: https://challenge-0221.intigriti.io/?assignmentTitle=∀㸀㳚ta+id=result㸀㳚ta+id=result+name=questionAnswer+value=alert(origin)㸀&assignmentText=&autosubmit=1
Interestingly, this only works for Chrome. Firefox does not create a DOM collection for window.result
with this payload. It might be possible in some other way, but this works for the challenge.