This is my write-up to the Intigriti's recent XSS challenge that I manged to solve in various ways, exploiting different vulnerabilities.
https://twitter.com/terjanq/status/1336248989633687553
The first solution I found was requiring user interaction. I haven't noticed a comment in the page source, that everything beneath it is considered "just a UI", and of course exploited the UI 😅 first.
The hardest part of constructing the payload was to realize that user interaction is not forbidden, but for some reason, I was very convinced that the challenge is about triggering the XSS without interaction. My intuition wasn't wrong because the intended solution indeed required no interaction.
The first solution was leveraging four facts:
#&num1=aaa&num2=123
searchQueryString
which was everything after the first occurrence of the question mark ?
. It was possible to inject ?javascript:alert(document.domain)//&
at the beginning of the URL, so the searchQueryString
would yield a valid javascript codeThen it was just a matter of assigning location=searchQueryString
to execute the payload.
PoC: https://terjanq.me/int_dec_sol.html
I was told by Inti that the intended solution required no interaction, but my solution was somehow close.
Didn't take me more than a few minutes to improve the solution to be triggering without user interaction.
The observation was that we can force the assignment onhashchange=init
which would reevaluate everything upon hashchange event
. Now, instead of requiring the user to click to invoke the vulnerable calc
function, we can manipulate the hash of the iframe, because changing the hash inside the iframe doesn't trigger the reload event.
PoC: https://terjanq.me/int_dec_sol_2.html
The other vulnerability I discovered was that when you click on a clear
button, num1
, num2
, and operator
parameters are deleted from the URL. This was actually the first attempt that I tried to exploit.
I noticed that when you have a URL with /?num1=smth&#num1=foo
and then try to remove the num1
parameter, the resulting URL will be /#&num1=foo
.
Because the page parses the URL in the following way:
?
.&
character as the delimiter.=
character as the delimiter.With my observation and user interaction, I could force the following chain of parsing the elements:
/?num1=calc&#&?num1=alert(document.domain)&num2=eval&operator=%3D
. Parsed parameters will be:
num1
:calc
num2
:eval
operator
:=
This forces the assignment calc=eval
Clear
(C
) button./#&?num1=alert(document.domain)&num2=eval&operator=%3D
and parsed parameters are:
num1
:alert(document.domain)
num2
:eval
operator
:=
This will trigger the invocation of eval('alert(document.domain)', 'eval', '=')
and which will trigger an alert.
PoC: https://challenge-1220.intigriti.io/?num1=calc&#&?num1=alert(document.domain)&num2=eval&operator=%3D
The last solution that I have submitted was combining all the previous solutions to achieve XSS that wasn't reusing any global
function or variables that were defined by the challenge, with the exception for window.onload
. This is very similar to my research towards Arbitrary Parentheses-less XSS.
The solution was to invoke the following chain:
onhashchange=onload
y=eval
decodeURIComponent=y
iframe.src='#?alert(document.domain)=1337'
PoC: https://terjanq.me/int_dec_sol_3.html