Walkthrough: SQL Injection - Blind

Tools Used:
When you first load the website, you'll see a url like so:

http://php1.hackthe.company/challenge17/index.php?group_id=3
You'll notice on the website that 5 pictures of bread have rendered.

If you add a single quote to the end of the group_id parameter in the url, you'll see the page breaks and no bread renders.

index.php?group_id=3'
If you add " and 1=1" to the end of the group_id parameter in the url, you'll see the page works properly and bread WILL render.

index.php?group_id=3 and 1=1
If you add " and 1=2" to the end of the group_id parameter in the url, you'll see the page breaks and no bread renders.

index.php?group_id=3 and 1=2
With this, we have a Boolean-based blind SQL injection. We have a method of determining true/false based on our injection - if we inject valid SQL that returns a row, the bread will render. If we inject invalid SQL, or if we inject SQL that does not return a row, no bread will render.

So first things first, let's determine what SQL database is being used. The MySQL query "SELECT @@version" will return a version string, like "5.5.42". We can then use the SQL substring function to check if just the first character of that version string is a 4 or a 5, like so: " and substring(@@version,1,1)=4" and " and substring(@@version,1,1)=5".

If you try both of those in the blind sql injection on this website, you'll see that the one matching MySQL 4 fails to render, while the one matching MySQL 5 succeeds. This tells us the app is running MySQL 5.

A brief aside. What we've been working on is a boolean-based blind sql injection. Sometimes in a blind SQL injection you won't have such a clear true/false case. In that event, you can use a time-based attack to achieve a true/false response.

MySQL has an if statement in this format: IF(condition, when_true, when_false)

Additionally, MySQL has a sleep command that waits for a specified number of seconds to pass - SLEEP(3) will make the app sleep for 3 seconds before responding, for example.

If you combine these, you can perform your test in the "condition" part of the if statement, and if true make the website wait for 3 seconds before responding, but if false make the app response immediately. As an example, if we insert the string " and IF(substring(@@version,1,1)=5, SLEEP(3), 0)" in our injection, then the server should wait 3 seconds before returning if it is running MySQL version 5. Change the test from MySQL 5 to MySQL 4 to see the server return immediately.

index.php?group_id=3 and IF(substring(@@version,1,1)=5, SLEEP(3), 0)
It's easier to use our established true/false case based off the bread rendering though, so we'll continue using that.

So we've established that this server is running MySQL 5. Our next task is to find the name of the table (which is the flag). From the challenge, we know the table name is prefixed with "flag_".

If we could just execute our own queries and see the returned results, we'd run a SQL query like this to retrieve the flag:

SELECT table_name FROM information_schema.tables where table_name like 'flag_%'
Alas, we cannot see the returned results. But using the same concept as we did earlier to check the MySQL version, we could actually test and find the name of the table, character by character, using true/false questions. For example, we could ask if the first character of the table's name (following the "flag_" prefix) is the letter a, like so:

index.php?group_id=3 and substring((SELECT table_name FROM information_schema.tables where table_name like 'flag_%'),6,1) = 'a'
When you try that, you'll see the site fails to render - telling us that the table does not start with "flag_a". Let's try "b" next.

index.php?group_id=3 and substring((SELECT table_name FROM information_schema.tables where table_name like 'flag_%'),6,1) = 'b'
Bingo! The breads render, which tells us the next letter of the table name is a b! We add that to our list and shift to guessing the next letter. We start with a...

index.php?group_id=3 and substring((SELECT table_name FROM information_schema.tables where table_name like 'flag_b%'),7,1) = 'a'
And so on. You can see where this is going. While it WILL get you the answer, it's also tedious. One way you could speed it up is instead of checking a single letter at a time, you could do the equivalent of a binary search - saying, for example: is the next letter between A and M in the alphabet? If so, is the letter between A and F? If so, is the letter between A and C? And now you can test A, B, and C individually. Instead of testing 26 letters, you can do a handful of range tests and then a few individual tests this way.

For an example, to test if the next letter is between "m" and "z", we can do the following:

index.php?group_id=3 and ascii(substring((SELECT table_name FROM information_schema.tables where table_name like 'flag_b%'),7,1)) > ascii('m')
The bread renders successfully, telling us that the next letter after "flag_b" is between m and z. You get the idea.

This is a little better than testing every letter one at a time, but still tedious. Can we do better?

Yes.

Download an application called sqlmap.

We'll execute it like this:
python sqlmap.py -u "http://php1.hackthe.company/challenge17/index.php?group_id=3" -p "group_id" --technique=B --dbms="mysql" --tables
We're basically just feeding into sqlmap the details we've already uncovered. The database system we've determined is MySQL, the "technique" is a Boolean-based blind ("B"), the parameter that is injectionable is the "group_id", and we want to dump the tables. You can see all the possible parameters to pass to sqlmap here.

When you execute this, it'll automate all the tests we were doing manually above, and in a few minutes should show you all the table names, including the flag.