Intigriti XSS Challenge 0221 Write-Up
XSS Challenge 0221
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 (
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.
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,
Now we can try to input
∼ which is
\u223c in the hope it will get converted to
">. 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
\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
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
<input> to achieve another level of depth, but
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.
<data id=result><data id=result name=questionAnswer value=alert(origin)>, we can set
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.