Reflected XSS at PentesterAcademy
Posted on Sat 09 August 2014 in Web Hacking
Here I will demonstrate 3 XSS attacks against 3 different challenges on Pentester Academy.
Pentester Academy has a large number of courses and challenges devoted to learning penetration testing and improving your skills.
My aim here will be first to demonstrate basic reflected XSS and then show how 2 different filters can be beaten.
XSS is the ability to execute JavaScript inside the browser of anyone who visits a specific webpage usually by injecting a combination of HTML and JavaScript.
Challenge 16: HTML Injection
The first one we'll look at is challenge 16.
This is the actual challenge page, if you browse to it, you should see this:
What I'm going to do is replace the whole form with one of my own which submit's to a server of my choosing and has an extra field but, otherwise, looks exactly the same as the real 1.
First, let's have a look at the vulnerability. First we need to see what happens when we submit a form:
I submitted the form with foo
in the username field and bar
in the password field. This is the full URL that I end up with:
http://pentesteracademylab.appspot.com/lab/webapp/htmli/1?email=foo&password=bar
As you can see, this was just submitted to the same page as a GET request. As this is the case, we can just manipulate this URL to test the fields, if the form had submitted a POST request, we'd have to keep submitting the form or use something like Burp Suite's Repeater feature.
You can see that the value of the email field has been reflected in the username input box. This is where we can test for a reflected XSS/HTMLi vulnerability.
Before that, let's check the source of this page to see in what context on the page our input has landed, right click on the page and click something like View Source:
So we've landed inside the value
attribute of an input tag.
Now let's check if we can use certain characters, send the following URL:
http://pentesteracademylab.appspot.com/lab/webapp/htmli/1?email=foo"<'()[]>&password=bar
It looks like theres little to no filtering here, we've managed to close the input
tag with the greater than (>) character that we sent, but let's look at the source:
So as suspected, there has been no filtering, this makes our job much easier.
Looking at the source code of the vulnerable form, we can figure out any required prefix and suffix:
1 2 3 4 5 6 7 8 9 |
|
All we should need to do here is break out of the value
attribute and the input
tag, to do this we'll need to put a double quote (") (because the value attribute was opened with a ") and >, respectively, at the start of our input.
We should now test for the classic alert box XSS payload with our prefix of ">
by sending the following URL:
http://pentesteracademylab.appspot.com/lab/webapp/htmli/1?email="><script>alert('xss')</script>
It worked, I put the alert statement inside script
tags, this is to tell the browser that this is JavaScript to be executed.
If you close the alert box and view the source you should see this:
Using this we can run any JavaScript we want, we just have to replace alert('xss')
, and as I will demonstrate this allows us full control over the page that is displayed.
The first thing we need to do is remove the current form so that we can put our own form in its place.
We can find all of the forms on the page using the getElementsByTagName
method.
The best way to build your JavaScript payload is to use Firebug, it allows you to write JavaScript dynamically while showing you what methods and attributes each object has avaliable.
If you open firebug, go to the Console tab and type document. if will show you a list of its methods and attributes.
If you look through the whole source of the webpage you will see that there is only 1 form
, and getElementsByTagName
returns an array containing all of the form
objects so to access the actual form we need to run document.getElementByTagName("form")[0]
to access the first element of the array:
Each object has a remove method, we can use this to remove the original form
.
Also, in JavaScript, all instructions can be put on a single line but they should be seperated by a semi colon (;).
Let's try using the XSS to first remove the form
using the method described and then trigger and alert box as we did before, for this we will use the following URL:
pentesteracademylab.appspot.com/lab/webapp/htmli/1?email="><script>document.getElementsByTagName("form")[0].remove();alert('xss')</script>
So that didn't work, let's look at the source and see what happened:
So it appears that our payload was cut off from the ;, we can solve this 2 ways, the first is easiest and most well known, replace the ; with a URL encoded version (%3b):
pentesteracademylab.appspot.com/lab/webapp/htmli/1?email="><script>document.getElementsByTagName("form")[0].remove()%3balert('xss')</script>
That works, but I also want to show you another method incase ;'s are blocked completely, ;'s can be replaced with comma's (,).
http://pentesteracademylab.appspot.com/lab/webapp/htmli/1?email="><script>document.getElementsByTagName("form")[0].remove(),alert('xss')</script>
From this point on I'll use ,'s to seperate the instructions when sent to the server but in my examples while building the JavaScript payload I'll use ;'s.
Now we need to create the new form
, we can do this using the createElement
and appendChild
methods, as well as the className
, innerHTML
, placeholder
, name
, type
and action
attributes.
Here is a full version of the Javascript that will build the form that we want and ensure it has all of the necessary attributes to make it look athentic:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
|
All of the information here, especially the class names, I got from the original form
. I've created a new form field on line 7 and set its settings on lines 10, 17, 18 and 19.
On line 30, I set the form action to http://localhost:9000/
, this means when the form is submitted it will send the request to localhost
on port 9000
, this could be set to any value/server under the attackers control.
The completed form
is contained inside the form variable.
The last thing to do is place the form at the right place on the page. If you look through the source, the form
is placed inside a div
tag with the class container
, before a div
tag with a class well
.
We can find both of these using the getElementsByClassName
method and we can insert it using the insertBefore
, here is the code for this:
1 2 3 |
|
Now we have all of the code we want to run, we just need to shrink the code as much as possible, we do this because in any exploit its best to keep the payload as small as possible so there is less chance of it being noticed.
Firstly all of the spaces need to be removed, in most situations spaces only make the code easier to read, next we can shrink all of the variable names down to 1 character, let's just take the first character of each as their name, unless use strict;
is used on the page (which it isn't) there is no need to declare the variables with the var
keyword and lastly we use the document
object repeatedly, we can create a variable with a 1 character name that point to it and use the variable instead (d=document;
).
After applying the rules above, moving everything to 1 line and changing the ;'s with ,'s you get the following code:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
We could probably shrink this down some more but this will do for now.
To send this payload we have to send the payload inbetween the script
tags, after that you should see the following:
Looking at the source we can see that it has been injected fine:
Python Capture Server
I've written a little python script using SimpleHTTPServer to capture these details:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
|
This server is set to print the values to stdout and then redirect to the actual application.
With the above python server running, when our custom form is submitted you get the following:
And the output on the python server's stdout:
1 2 3 4 |
|
So that is challenge 16 completed for what we wanted to achieve and everything is transparent to the end user, you just need to send the malicious link to the target.
Challenge 16 Secure
The next challenge is here, some filtering has been added to mitigate the previous exploit.
First let's look at the challenge:
This looks exactly the same as the last challenge, so let's use the application and see if that is the same:
So far everything looks the same, even the URL we are sent to:
http://pentesteracademylab.appspot.com/lab/webapp/htmli/1/secure?email=foo&password=bar
Let's analyse this application the same way as before by sending the following URL:
http://pentesteracademylab.appspot.com/lab/webapp/htmli/1/secure?email=foo"<'()[]>
Looks interesting, let's look at the source:
So < and > has been encoded but " hasn't, looks like we'll have to use an event handler to run our JavaScript this time.
Ideally we want the event handler to run without any interaction, a lot of the event handlers require some interaction.
We are landing inside an input
tag and 1 event we can hook is the onfocus
event, but we need to make sure that the input
box is in focus when the page loads, for this we can use the autofocus
attribute.
So we now need a new prefix for our payload, we need to close the value attribute, with a ", we then need a space and the autofocus
keyword, then a space and lastly onfocus="
, so we end up with:
" autofocus onfocus="
After this there is no need to put any script
tags, we can't anyway because < and > gets encoded.
Let's try executing an alert box to test if XSS works here, we need to send the following URL:
http://pentesteracademylab.appspot.com/lab/webapp/htmli/1/secure?email=" autofocus onfocus="alert('xss')
So we can now run JavaScript on this page, we will recreate the exact same attack as last time, the only change we need is to replace every " with a single quote ('), then we end up with this as our JavaScript:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
Sending this in place of alert('xss')
in our previous request gives us the following:
Looking at the source, we can see how our payload got interpreted:
Now we are in the same position as we were when we'd got our custom form on the other page.
Last Challenge: DOM XSS
This is the last challenge I'd like to demonstrate.
Even though this challenge is very different I want to create the same exploit where I create a custom form and put it on the page in a similar position as the previous examples.
Its quite a bit more difficult to exploit but let's get to it and have a look at how it works:
Its clearly doing some maths here based on the value of the statement
argument given in the address bar, let's look at the source:
So we are landing inside script
tags and our input is being used as an argument to eval
.
This time, however, we can't see how our payload is being interpreted directly.
We should be able to run any JavaScript inside here though, let's try a normal alert box by sending the following URL:
http://pentesteracademylab.appspot.com/lab/webapp/jfp/dom?statement=alert('xss')
That didn't work, let's open Firebug, open the console tab and try again (this should show us any error's that happened while it was executing any JavaScript):
So the problem is that the ' are URL encoded... This is because, as you can see from the source code, it is accessing the argument using the document.URL
property where certain characters are URL encoded so we will be unable to use any types of quotes (' or ").
There are probably a few ways to beat this problem, an obvious 1 is to avoid using strings but we are unable to do that here.
The way I like to get around this is to use String
objects and using forward slashes (/) at the beginning and end to imply it is a regular expression.
Let's try to execute an alert using this method, we need to send the following URL:
http://pentesteracademylab.appspot.com/lab/webapp/jfp/dom?statement=alert(String(/xss/))
So it worked but we have / surrounding the string, we can use the substring
method and the length
property to remove these, we need to send the following URL:
http://pentesteracademylab.appspot.com/lab/webapp/jfp/dom?statement=x=String(/xss/),alert(x.substring(1,x.length-1))
We will be using the String
and substring
methods a lot, so it would be best if we create aliases for these to shorten our payload, we can create a function for the substring
section like this:
y=function(z){return/**/z.substring(1,z.length-1)}
I have used /**/ here because we are also unable to use spaces (they are URL encoded too) and this just acts as a comment.
This function takes 1 argument and returns the string with the first and last character removed.
We can create an alias for the String
method using this code:
S=String
Before we start to write our payload, let's test this with an alert by sending the following URL:
http://pentesteracademylab.appspot.com/lab/webapp/jfp/dom?statement=S=String,y=function(z){return/**/z.substring(1,z.length-1)},alert(y(S(/xss/)))
So it works, lastly all we need to do is remove the string Mathemagic
and the div
tag that contains the result.
Looking at the source we can see that the Mathemagic
string is contained in a h2
tag and there are no other h2
tags on the page, so we can find this using the getElementsByTagName
method.
The result is contained inside a div
tag which has the id
value set to result
, so we can find this using the getElementById
method.
Both of these we can remove using the remove
method.
We are now ready to write our payload, here is the "beutified" version of the payload, remember that this all goes on 1 line and with , seperating the instructions and not ;:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
|
So using this the URL that you will need to send is:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
After sending this URL you should see the following:
PWNED!!! :-)
Conclusion
For the last to exploits, the redirection URL of the python server would have to be changed.
XSS exploits can vary greatly, but as long as you can get JavaScript to run you should be able to get full control over the page.
There are various methods for bypassing different filters and I've only mentioned a couple here but the methods that you use will highly depend on the filter that you are facing.
A lot of trial and error is needed to determine how best to bypass the filter than is in place.
In each of these examples, to take advantage of the exploit, you need to send the URL that we have created to the victim. A URL containing all of this information might look very strange to the victim so it might be best to URL encode the whole payload, you can do this in BurpSuite's Decoder tab or on a website like this, its worth noting though that Burp will URL encode all of the text (incuding any alphanumeric characters), that website (like most) will only encode certain characters.
Further Reading
OWASP is the authority on web security so their website contains any relavent information regarding this.
The OWASP XSS page and XSS filter evasion cheat sheet are very good resources.
Also, the OWASP testing guide has a great page on how to go about testing for XSS.