[Intigriti XSS challenge] May 2021
I solved Intigriti’s 0521 XSS challenge, which was a guest challenge created by @GrumpinouT.
After a first inspection, there’s not a lot going on on the front
page, so the captcha.php
is probably a more interesting
target. There are several named spans on that page with simple letters,
so they seem likely to be something that can be controlled by sending
query parameters to the PHP. This though is quickly confirmed,
a
, b
, c
and d
can
all be set, but unfortunately a few quick tests seem to indicate those
fields are all properly sanitized. Furthermore the contents of
a
and d
are reset to random integer values
once the page loads.
Looking at the javascript code on this page, we can easily spot an
eval, with its accompanying character blacklist. The comment there
points us at an interesting hole: the letter e
is available
“because it’s a mathematical constant”. Some other characters that are
available and will be useful are `[]{}$+/.
and all digits.
The value of e
is however not a mathematical constant in
this case, but the global variable referencing the DOM object with id
e
.
From here, we start having access to several strings, so we can start
collecting letters and can start doing an alternative version of the
ever-classic JSFuck. One variant that gets extremely close to
what we have access to is jsf$ck, but we don’t have
access to the critical character !
. Instead, let’s start
finding our own gadgets to build strings out of. The final
payload we’re aiming for would be
e['constructor']['constructor']['call']`${"alert(document.domain)"}```
which will use tagged templates to create a new function with our payload and immediately call it.
Side note: We need the ['call']
because
otherwise the string interpolation will pass too many arguments to
Function
and we just get exceptions.
So the strings we need to form are:
constructor
call
alert(document.domain)
Already by using `${e}`
, we can get the string
"[object HTMLProgressElement]"
, which has a lot of the
letters we need. We’re only missing a
, u
and
parentheses. A u
, we can get from undefined
,
as value which is easy to get essentially anywhere in javascript, and
which we can cast to a string by adding it to ``
, in
particular, the choice is made for [[][0]+``][0]
. To get an
a
, we look for more interesting javascript constants, and
we find NaN
, which we can get by dividing 0/0
.
The parentheses took the longest to find, but remembering that a
function’s toString()
contains the entire function,
including formal parameters, we can use
[]['constructor']+``
to get something starting with
function Array()
. Note that we already have enough letters
to form that string 'constructor'
.
From there, it’s a matter of combining all these letters into one big
string, create a URL and have some unsuspecting victim click on the
submit button despite a wall of suspicious text being in the formula.
Rather than doing everything by hand, I opted to make a quick python
script that will generate it for us. One last pitfall we encounter along
the way is that we need to sandwich our exploit with +
,
since the numbers a
and c
will be concatenated
to both sides.
The script is presented below, and is fairly self explanatory:
import urllib.parse
def findletter(s):
for k, v in blocks.items():
if s in v:
return k + f"[{v.index(s)}]"
assert False, s
def find(s):
return '+'.join(findletter(t) for t in s)
blocks = {
'`${e}`': "[object HTMLProgressElement]",
'[[][0]+``][0]': "undefined",
'[[0/0]+``][0]': 'NaN',
'`.`': '.',
}
CONSTRUCTOR = find("constructor")
blocks[f"[[][{CONSTRUCTOR}]+``][0]"] = "function Array()"
CALL = find("call")
PAYLOAD = find("alert(document.domain)")
print("https://challenge-0521.intigriti.io/captcha.php?c=0&b=" + urllib.parse.quote('+e[%s][%s][%s]`${%s}```+' % (CONSTRUCTOR, CONSTRUCTOR, CALL, PAYLOAD)))