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:
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:
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.
If that works, just shove in the database version where ‘hacker’ was.
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.
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.
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.
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:
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:
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:
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.
At this point, I can just type
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.