During the last edition of HITB in Amsterdam we partecipated in the XSSGame by Google: 8 XSS challenges to win a Nexus 5X. The various levels exposed common vulnerabilities present in modern web apps.
Each level required to trigger the JavaScript’s alert function by creating an URL with a Cross-Site Scripting (XSS) payload inside, which should be executed without any user interaction: once it is executed, the server replies with the link to the following challenge.
A search bar is available, on submit the input query is printed into the page HTML code. All we have to do is search for <script>alert(1)</script>
and an alert will popup on page load.
A form containing a timeout input value is available, on submit the page will wait for the seconds entered and popup an alert. Looking at the HTML source code it is possible to guess that the timeout value entered (timer
GET parameter) is directly inserted into the startTimer()
JS function of the onload
HTML attribute in
|
|
potentially leading to a JavaScript code injection.
Requesting <challenge url>/?timer='-alert(1)-'
leads to code injection and Level 3.
XSS library
is a gallery of cat pictures, allowing us to navigate through the pictures. Only the fragment identifier is changed on picture change.
Looking at the source code we can see the function chooseTab
, called on url change, appends the fragment identifier (user_input
) to the src attribute of an img:
|
|
Playing with the fragment identifier allows us to exploit the DOM based XSS injecting JavaScript code into an arbitrary HTML attribute, for example “onerror”: <challenge url>/#1'onerror=alert(1)>
(It’s important to note that this payload would not work in the latest versions of Firefox since it url encodes the fragment identifier)
A Google Reader 2.0
homepage is presented to us, with registration available.
No reflection seems to be available during the procedure, but we observe that just after the registration a redirect is executed, bouncing us back to the homepage. The parameter next
in <challenge url>/confirm?next=welcome
lead us to think welcome
is some sort of page identifier, looking at the source code the vulnerability is evident:
|
|
Requesting <challenge url>/confirm?next=javascript:alert(1)
triggers the alert.
A Google-ish search bar, again. Now AngularJS-flavoured, the web app ships with a vulnerable version of the famous JavaScript framework, 1.5.8. The form inputs utm_term
and utm_campaign
are set from the request parameters, if any.
The first things that come to mind when looking for vulnerabilities in an AngularJS-powered web app are template injections and AngularJS sandbox escapes (which from version 1.6 have been removed).
It is easy to find the correct payload in order to escape the sandbox of the JavaScript framework and insert it in one of them:
<challenge url>/?utm_term=&utm_campaign={{x={'y':''.constructor.prototype};x['y'].charAt=[].join;$eval('x=alert(`1`)');}}
Like level 5, a search bar and a vulnerable version of AngularJS (1.2.0) are available. Again the payload is not directly usable through the search bar because the query string entered is used inside of a ng-non-bindable div element, which as the documentation reports it is not interpreted by Angular at runtime.
After some dumb-fuzzing we notice that:
action
attribute;£
in the URL make the page throw a 500 error (later it’d be marked as a bug, ahah);{
character is deleted from the URL on page load.Looking into the first and third notes we try to use the HTML entity version {
, which works and it’s been interpreted – from here we can encode all the braces in the payload and spawn our alert: <challenge url>/?query={{a='constructor';b={};a.sub.call.call(b[a].getOwnPropertyDescriptor(b[a].getPrototypeOf(a.sub),a).value,0,'alert(1)')()}}
A blog page loads content using the menu
GET parameter and JSONP requests, the OK
response contains title
and pictures
attributes (which will be used to populate a h1
and few img
HTML elements), while an ERROR
response contains only the title
attribute. In the description this challenge is described as a “common CSP bypass”; unfortunately we haven`t the screenshot for this one.
XSS is possible inside of the base64-encoded menu parameter, but contrary to previous challenges (where it wasn’t present), CSP is defined as default-src https://hitb.xssgame.com/static/ <challenge url>
.
The callback
parameter in the JSONP request can be used to inject valid Javascript code into the response, which will be interpreted client side, for example:
|
|
returns
|
|
We can use the JSONP endpoint as src
of a script
element in order to bypass CSP and execute the alert: <challenge url>?menu=base64_encode(<script src="jsonp?callback=alert(1)%3b%2f%2f"></script>)
(like previously, base64_encode is only used for better readability, final payload is already base64 encoded)
The last one mixes the previous challenges into one: “the exploit must work for any user, logged in or not, and CSRF, self-XSS and CSP should be exploited in order to win”, the introduction says.
It is possible to execute bank transfers logging into an account by username (optional) and sending a transfer with name
and amount
values. After the login a username
cookie is set containing the username entered, name and amount of the transfer are sent as GET parameters, alongside a random 16 bytes CSRF token saved as cookie. CSP is defined like level 7.
Looking into the transfer procedure it is clear the amount field is vulnerable to reflected XSS and the CSP is not defined, however it is just the self-XSS
part of the challenge and it works only with a csrf_token
parameter matching the homonym cookie.
During the login procedure we notice a request to <challenge url>/set?name=username&value=<username entered>&redirect=index
, which would set our username
cookie and send us back to the homepage.
Using this “feature” we can set an arbitrary cookie with an arbitrary value and redirect the user to an arbitrary page. In our case we can set the csrf_token
cookie and redirect the user to /transfer
where the transfer will be executed because the cookie and the csrf_token
GET parameter will then match: <challenge url>/set?name=csrf_token&value=arbitrary&redirect=url_encode(/transfer?name=attacker&amount=3"><script>alert(1)</script>&csrf_token=arbitrary)
(using url_encode for better readability, the argument should be url-encoded in the URL.)
Thanks HITB for the the great conference and Google for the Nexus 5X! 🤟🏻