Console Wars Part 2: SQL injection

By |Published On: December 16th, 2021|Tags: |

Where we are now

If you are just joining us, our hero from part 1 of “Console Wars” has found himself in a bit of a pickle. 

That’s me. I’m the hero, and I am in a tough spot. 

I am performing a penetration test where I just found SQL injection. But it is not a normal SQL injection–the vulnerable HTTP requests are obfuscated and I don’t know how to build my own requests with my own queries. 

So far I’ve gotten around this restriction by using the web console to issue requests the way the application wants. But how am I going to dump the database? 

Dumping the Database via the Web Console

I am a professional–I need to demonstrate just how bad this SQL injection can be. 

Auth bypass is good, but dumping password hashes is better. Cracking a random user’s password–one that was not provided for the test–would be a very good piece of evidence for the report. 

Easy mode SQL injection is achieved with tools like sqlninja. In this case, though, how can we tell sqlninja to encode the request if we can’t do it ourselves? Fine! I’ll make my own sqlninja, with JavaScript and the console.

Pro tip: In general, if login has SQLi, then so does everything else. Don’t get too excited and exploit a slow database dump through a blind login injection. Instead look for the most verbose injection to exploit.

First things first: I figured out how to encode a request without changing what web page I was on–but I don’t remember exactly how I did this. Even if I did remember, I would lie about it anyways. I am not going to share details of a test. This is just the general idea. Regardless, I ended up creating a function that returned the truthfulness of a SQL statement. Lets just pretend it looked like the following:

Copy to Clipboard

I likely wrote this in a vim and then pasted it into the web console. Now that sql is a function we can very easily test SQL statements in the web console with sql(“1 = 1”)

Pro tip: Large databases are slow, especially if you are asking for EVERYTHING in the database. So don’t base your SQL injection off of a “or 1=1–” type payload. Limit the results to a single entry on success or nothing on failure. 

21 Questions with Mr. SQL

I usually do Blind SQL injections by writing my own script. It’s fun–it’s like playing a game of 21 Questions with your ol’ buddy Mr. SQL. Taking this approach and automating whatever parts get boring, you can write your own SQL injection script in no time. Even in the console.

I always start with the dumbest questions first. These are intended to be sanity checks. For example, you could ask “Hey Mr.SQL! Are there more than 0 characters in the word ‘hacker’?”. Since Mr. SQL does not speak English, you have word it his way:

Copy to Clipboard

Now, if Mr. SQL answers with “false,” I know something is wrong. My code is broken, the SQL statement is not right, there’s bad encoding, or maybe even this isn’t SQL injection at all. If MR. SQL answers “true,” I progress slowly to ensure Mr. SQL is trustworthy before asking more exotic things. So, I might ask the same ‘hacker” question again, but this time with a sub-query.

Copy to Clipboard

If that works, just shove in the database version where ‘hacker’ was.

Copy to Clipboard

Notice that each time I do this, I am just hitting “up” on the console to get my last request, then modifying it just a little bit. In a couple of minutes, you can ask a lot of questions. Especially if you don’t use that stupid capitolized syntax. 

Guess the Number with Mr. SQL

At this point, I started playing the 21 Questions sub game called “guess the number.” I essentially ask Mr. SQL to think of how many characters are in the version string. I then try to guess that number with only “is it bigger than” questions.

Copy to Clipboard

This is just the divide and conquer idea (aka binary search, aka bit banging). If the number we are guessing is not greater than 1024, I can half the number and try again. I start with 1024 because it is a power of 2. So you can divide it by 2, and you can divide the result by 2 again and so on. 

Mr SQL has told us the number he is thinking of is less than 1024, and it’s also less than 512, and so on until we hit 64. Mr. SQL is thinking of a number bigger than 64 and smaller than 128. So I do the divide and conquer method again to ask how much bigger than 64? So is it 32 bigger? Be sure to be lazy and let the console do the math for you.

Copy to Clipboard

I always do the above manually at first. This helps me find small simple bugs early. Eventually, the above manual process becomes too grueling so I automate it with our second function.

Copy to Clipboard

Running verlen() will give you the length of the SQL server’s version by playing the 21 questions game with Mr. SQL. Let us assume the answer came out to be 78, the version is 78 characters long (again, I am making this up). We can validate our work so far by asking Mr. SQL “Is your version 78 characters long?” This is done with our old SQL function:

Copy to Clipboard

Once I was confident that this worked, I changed the verlen function to be simply a “guess the number” function. It probably looked something like this:

Copy to Clipboard

Time for another sanity check, let’s check the length of the word “hacker.” The use of the “bits” parameter above means we can ask fewer questions if we know the upper limit Mr. SQL is thinking of. For the length of “hacker,” we know 6 is less than 2^4, so we can verify the above code with just 4 requests. Like so:

Copy to Clipboard

Dumping Strings

Once I could confidently guess any number Mr. SQL was thinking of, I started asking Mr. SQL to think of more interesting numbers. I asked Mr. SQL to “think of the first character of your version string, but turn it into a number.” In SQL this is something like: ascii(substring(1,0,@@version)). This SQL command just returns a number, so I can just give this to my number guessing function. 

The number I get back can be converted into a character with the JavaScript function String.fromCharCode. Don’t worry about doing this part with a known string–at this point you should have a decent idea of what the beginning of the version string looks like. I would still do this all manually at first, character by character. Once I hate myself again, I automate it with another JavaScript function:

Copy to Clipboard

At this point, I can just type dumpStr(“@@version”) in the console and the code will dump the version string. It will take 10 requests to get the length of the string, then 8 requests per character in the string. We only needed to make 3 JavaScript functions (sql, getNum and dumpStr). 

It probably took me a couple hours to get here. From here, I mostly manually found the user table and then wrote another loop to dump each user and password hash pair from the database. About 4 hours after the test started, I was off getting lunch while my custom SQL injection script churned out password hashes in the console.

Pro tip: Requests are slow, so avoid making them as much as you can. Hashes are constant length, so you don’t need to ask the length each time. 

Pro tip: With a SQLi auth bypass, there is no reason to dump the username character by character. Just use the auth bypass with a dumped password hash to authenticate as the associated user. Now you can just look at the user’s profile to get the username.

Conclusion

JavaScript is not a completely worthless language; with just a few lines, we can make our own SQL injection script. Doing it ourselves means we have full control over the injections and can save time and database queries. SQL injection is very uncommon nowadays and this was a strange situation, but you can take a more general lesson from all this. Learn how your tools like sqlninja work so that when the tool fails you can still succeed.

About Hurricane Labs

Hurricane Labs is a dynamic Managed Services Provider that unlocks the potential of Splunk and security for diverse enterprises across the United States. With a dedicated, Splunk-focused team and an emphasis on humanity and collaboration, we provide the skills, resources, and results to help make our customers’ lives easier.

For more information, visit www.hurricanelabs.com and follow us on Twitter @hurricanelabs.