A guide to blind SQL injection
How does blind SQL injection work? How are the queries structured and how can you extract data from the backend without seeing any responses.
This nugget will guide you through the structure of a blind SQL query and will hopefully help clarify how to constuct them yourself.
Building the Query
Let's take the following SQL query running in the backend:
Q: select count(*) from ‘posts’ where id=[USER INPUT]
Where the attacker can control the field "[USER INPUT]". We now assume that no response will be returned to the user which is a very common scenario. We will approach this using blind SQL injection. We can try the following query to verify that our injection works:
Q: select count(*) from ‘posts’ where id=1 union select sleep(5)
We notice that the server indeed takes 5 seconds to reply to our request. We can now format our query so that it will only result in a sleep depending on some internal condition.
Q: select count(*) from ‘posts’ where id=1 union select if([BLIND_INJ], sleep(5), null)
Where [BLIND_INJ] can be any type of subquery. For example:
[BLIND_INJ]: select 1 -> Will result in a 5 second sleep.[BLIND_INJ]: select 0 -> Will not result in any sleep.
In our case, we want to make [BLIND_INJ] a query that will return true if a certain character in a certain string equals the character that we are testing:
[BLIND_INJ]: (ascii(substring(([TARGET]),[INDEX],1))=[CHAR])
Where:
[TARGET]can be any subquery resulting in a string, such as “select ‘aaaa’”.[INDEX]refers to the character in[TARGET]that we want to test.[CHAR]refers to the (integer representation of the) character we want to test.
We can then use this query to extract data from the database character by character. For example:
Q: select count(*) from ‘posts’ where id=1 union select if((ascii(substring((select ‘abcde’),2,1))=[CHAR]), sleep(5), null)
Will result in a server response delay of 5 seconds only if we enter a value for [CHAR] that is the integer representation of ‘b’ and no delay otherwise.
As a result, we can just try all possible values for [CHAR] until we notice that the server takes more than 5 seconds to respond.
We can speed this up by doing a binary search on the possible characters:
[BLIND_INJ]: (ascii(substring(([TARGET]),[INDEX],1))<=[CHAR])
Instead of checking if a character is equal, we check if it is smaller or bigger than our given test character. This results in substantially less tests that need to be executed.
Once a character is found, we increase the value of [INDEX] and continue. Now, ofcourse we want to obtain some useful data from the database.
In our case, lets assume we want to steal session tokens from the server. We are interested in the tokens table. This table contains the session tokens for the (recently) active users.
If we can extract these tokens from the database, we can use them to impersonate those users.
We create a better subquery for our [TARGET]:
[TARGET]: select concat("*", token, "*") from tokens order by date desc limit 0,1
We add the two * to the start and end of our query as a double check to make sure we obtained the entire string.
This is useful in case latency causes the server to respond too slowly. We select only the first token from the table ordered by the date field.
This allows us to select only the most recent token. This is not mandatory but it seems like good practice to steal a recent token in case it expires.
Putting it all together we get the following query:
Q: select count(*) from ‘posts’ where id=1 union select if((ascii(substring((select concat("*", token, "*") from tokens order by date desc limit 0,1)),[INDEX],1))<=[CHAR]), sleep(5), null)
We now execute this query over and over again for different values of [INDEX] and [CHAR] to extract the token from the database.
For every value of [INDEX] we do a binary search on the values of [CHAR] until we determine the character for that index position.
Then we increment [INDEX] by one and continue until we are done.
