Tuesday, 6 January 2015

My first bug bounty

Adventures in XSS

Yesterday I submitted my first bug bounty which felt just as good as I thought it would, great success. The exploit is on www.ziggo.tv, it's only a basic reflected XSS exploit but it was fairly hard won as they have extensive protection to deal with user input. It turns out that despite a bug bounty description that offers compensation in exchange for bugs, they don't think XSS is dangerous enough to warrant paying out. Normally I'd be more discreet with security issues but I thought that was a bit of a dick move on their part so I've decided to publish instead and at least get an interesting blog post out of it.

Cross Site Scripting

If you're not familiar with XSS then please take a look another of my posts which explains XSS attack vectors in depth, you can find it here. The TL;DR version is that XSS is an exploit that allows attackers to inject JavaScript into someone else's website via an abuse of user submitted content, any users who browse the website are open to any number of attacks.

In this case the user submitted content was a search feature on their media site, if you enter a search term you'll get back a very ugly looking URL including an array of page parameters, one of which is the search term. This is the result of searching for "test".

You can see the search_by_keyword parameter at the end, it is set to a value of test, the basic attack methodology of reflected XSS is to craft a malicious link and then send that link to a victim(s), or simply post it on another site and encourage people to click on it. Only those people following the specially crafted URL will be affected by the vulnerability, everyone else arriving at the search page naturally will never be affected.

Trial and error

Finding XSS vulnerabilities usually involves a lot of trial and error, you're blind as to how the server processes input so you need to present specific inputs and test to see how the server behaves and what it returns, this helps you build up a mental image of the method by which they processes data and return it to the user, it almost always has some kind of sanitization that removes potentially harmful code.

The first thing to do is just throw in an attempt to open up a script tag and see the behaviour, let's try the parameter

Input

search_by_keyboard=test" /><script>

This does several things, first of all having the word test in there helps me find the result in the source code of the page that is returned, that just makes iterative testing much faster. The " /> closes out the input box we're injecting this into. In the HTML on the result page you'll find the search box pre-populated with the old search result which is the source of the injection, this is where your search term is being returned to the page. It looks something like this normally

You can see that by using the input test" /><script> we can prematurely close the value property and then close the input tag itself, leaving us able to write valid HTML such as <script> tags.

However the result in this case was that <script> was replaced with [removed], they're specifically looking for script tags and replacing them with something harmless, this is the first piece of the puzzle that is their user input sanitization.

The next step I took was looking at attack vectors that didn't use <script> tags explicitly, there's a huge number of these but a really easy one is to give HTML elements some event attributes, events are triggered when some specific condition occurs, they can be when a user hovers a mouse over an element, or it could be when an error is encountered, writing deliberately malformed HTML inside a tag and including an onerror event attribute is a great way to automatically trigger itself and run the Javascript that is associated with the event. It can be done like this

Input

search_by_keyboard=test" onerror="" />

You simply put your Javascript inside the onerror attribute, in this circumstance input elements don't support onerror attributes, but the point isn't to exploit straight away, it's to map out what sort of characters are allowed and see if anything is modified or removed. The result we get is unexpected:

It looks like they've removed the onerror="" but they've kept test, they obviously have some kind of blacklist of denied strings. There is however a benefit to this, unlike the detection of <script> which is replaced with [removed], these attributes are just deleted, that has the extremely helpful property of collapsing together text that exists either side. For example

ABCDonerror=""EFGH

becomes simply

ABCDEFGH

There is the potential for us to use this to help beat other parts of the filter, if we can work out the order the filters are applied then we can pass the first one by splitting up our key words and have the other filter reassemble them. Let's try breaking up a script tag:

Damn, so close. <script> now evades being replaced with [Removed], however they've safely HTML encoded the angle brackets into &lt; and &gt;, and oddly the close quote ends up at the end rather than closing the value attribute like it should.

If there was only a method to stop the angle brackets from being HTML encoded, unfortunately you c cannot use the same trick of splitting up text in order to obfuscate it when you're only dealing with a single character...or can you?

Enter URL encoding to save the day, all text can be URL encoded, it just so happens the site is taking URL encoded input and it's decoding it for us before usage, that's handy. So we can represent < and > as their URL encoded counterparts which is %3C and %3E. These on their own are correctly URL decoded by the server and are treated like regular brackets, but there's nothing to stop us from breaking up the URL encoded variants with an event tag. Let's try.

Input

search_by_keyboard=test"" />%3onerror=""Cscript%3onerror=""E

You'll also note I've added a 2nd quote after test, this is to try and keep the HTML mark up well formatted by adding in another quote as one alone is removed. It's a success, our output is now

Let's add that in, wrap the end in a close script tag </script>, we'll need to use the same trick with onerror="" to obfuscate it. Let's also clean up after ourselves to make sure we're not leaving behind any malformed HTML, when we prematurely closed the value attribute and then closed the input tag we left behind characters that were designed to do this. So let's open up an extra html element ourselves and not close this tag, it will be closed by the characters left behind. For this purpose a half open anchor tag is fine - this keeps the page looking the same as before and masks the attack. Our final URL is