Blind SQL Injection
Boolean-Based SQLi
Consider an application that uses tracking cookies to gather analytics about usage. Requests to the application include a cookie header like this:
When a request containing a TrackingId
cookie is processed, the application determines whether this is a known user using an SQL query like this:
This query is vulnerable to SQL injection, but the results from the query are not returned to the user. However, the application does behave differently depending on whether the query returns any data. If it returns data (because a recognized TrackingId
was submitted), then a "Welcome back" message is displayed within the page.
This behavior is enough to be able to exploit the blind SQL injection vulnerability and retrieve information by triggering different responses conditionally, depending on an injected condition. To see how this works, suppose that two requests are sent containing the following TrackingId
cookie values in turn:
The first of these values will cause the query to return results, because the injected AND '1'='1
condition is true, and so the "Welcome back" message will be displayed. Whereas the second value will cause the query to not return any results, because the injected condition is false, and so the "Welcome back" message will not be displayed. This allows us to determine the answer to any single injected condition, and so extract data one bit at a time.
For example, suppose there is a table called Users
with the columns Username
and Password
, and a user called Administrator
. We can systematically determine the password for this user by sending a series of inputs to test the password one character at a time.
To do this, we start with the following input:
This returns the "Welcome back" message, indicating that the injected condition is true, and so the first character of the password is greater than m
.
Next, we send the following input:
This does not return the "Welcome back" message, indicating that the injected condition is false, and so the first character of the password is not greater than t
.
Eventually, we send the following input, which returns the "Welcome back" message, thereby confirming that the first character of the password is s
:
Error-Based SQLi
In the preceding example, suppose instead that the application carries out the same SQL query, but does not behave any differently depending on whether the query returns any data. The preceding technique will not work, because injecting different Boolean conditions makes no difference to the application's responses.
In this situation, it is often possible to induce the application to return conditional responses by triggering SQL errors conditionally, depending on an injected condition. This involves modifying the query so that it will cause a database error if the condition is true, but not if the condition is false. Very often, an unhandled error thrown by the database will cause some difference in the application's response (such as an error message), allowing us to infer the truth of the injected condition.
To see how this works, suppose that two requests are sent containing the following TrackingId
cookie values in turn:
These inputs use the CASE
keyword to test a condition and return a different expression depending on whether the expression is true. With the first input, the CASE
expression evaluates to 'a'
, which does not cause any error. With the second input, it evaluates to 1/0
, which causes a divide-by-zero error. Assuming the error causes some difference in the application's HTTP response, we can use this difference to infer whether the injected condition is true.
Using this technique, we can retrieve data in the way already described, by systematically testing one character at a time:
Time-Based SQLi
In the preceding example, suppose that the application now catches database errors and handles them gracefully. Triggering a database error when the injected SQL query is executed no longer causes any difference in the application's response, so the preceding technique of inducing conditional errors will not work.
In this situation, it is often possible to exploit the blind SQL injection vulnerability by triggering time delays conditionally, depending on an injected condition. Because SQL queries are generally processed synchronously by the application, delaying the execution of an SQL query will also delay the HTTP response. This allows us to infer the truth of the injected condition based on the time taken before the HTTP response is received.
The techniques for triggering a time delay are highly specific to the type of database being used. On Microsoft SQL Server, input like the following can be used to test a condition and trigger a delay depending on whether the expression is true:
The first of these inputs will not trigger a delay, because the condition 1=2
is false. The second input will trigger a delay of 10 seconds, because the condition 1=1
is true.
Using this technique, we can retrieve data in the way already described, by systematically testing one character at a time:
Optimization
One of the best optimizations you can do to your blind SQLi exploitation algorithm is to reduce the number of iterations you have to do per character. This means that you need to be able to determine the charset:
string.ascii_uppercase
, orstring.lower_uppercase
, orstring.printable
The idea is to execute the following two queries:
Query 1:
Query 2:
Evaluate the results:
If
Query 1 == True
andQuery 2 == False
, then the charset isstring.ascii_uppercase
.If
Query 1 == False
andQuery 2 == True
, then the charset isstring.ascii_lowercase
.If
Query 1 == True
andQuery 2 == True
, then the charset isstring.printable
.
Reference
Last updated