Archives

All posts for the month May, 2019

On a recent engagement I encountered a drupal site which allowed for some interesting file uploads. The first file upload form had a whitelist of allowed extensions which I was not able to bypass. The second accepted archive formats including tar, zip, and bz2. It would extract the archive and place in the contents into the /files directory of the drupal site.

The file extension whitelist was not applied to extracted files and it was also possible to include subfolders which structure would be maintained. This meant I was able to include .php files and put them in their own subdirectory. There was a problem though, which is that in drupal the /files directory contains a .htaccess file with the following contents:

# Turn off all options we don't need.
Options None
Options +FollowSymLinks

# Set the catch-all handler to prevent scripts from being executed.
SetHandler Drupal_Security_Do_Not_Remove_See_SA_2006_006
<Files *>
  # Override the handler again if we're run later in the evaluation list.
  SetHandler Drupal_Security_Do_Not_Remove_See_SA_2013_003
</Files>

# If we know how to do it safely, disable the PHP engine entirely.
<IfModule mod_php5.c>
  php_flag engine off

This post explains that SetHandler Drupal_Security_Do_Not_Remove_See_SA_2006_006 sets a non-existant execution handler so that php code inside this folder will not run. Additionally it sets php_flag engine off which should also prevent php code execution.

To get around this and get my php code to run I included a .htaccess file in the zip archive which did successfully get extracted, but for some reason a lot of the things I tried in order to enable php did not work. I wanted to share the working .htaccess for my future self and everyone else as a reference for what to do when in this kind of situation. The answer came from here:

php_flag engine 1
<FilesMatch "\.(php5?|html|phtml|php)$">
  SetHandler application/x-httpd-php
</FilesMatch>

In my time as a pen tester sqlmap has been an extremely valuable tool. Miroslav Stampar deserves a big salute for creating and maintaining sqlmap. THANK YOU!! So in this post I’m going to talk about a few situations where sqlmap was not working out of the box for one reason or another, and how I was able to work around it.

Sleep Amplification

First up is a time based MySQL injection vulnerability where the sleep time is amplified heavily. I’ve seen this before, where the sleep time gets multipled by 2 or maybe some more. In this example the sleep time is multiplied by about 18,000 :). The following screenshot shows the application sleeping for 18 seconds when we tell it to sleep for .001 seconds, this is a consistent result:

sqlmap actually could deal with this on its own, but the speed was not even close to optimal. My solution (before I knew how easy it was to create tamper scripts) was to proxy sqlmap to burp and do a find and replace within burp. I would search for the string sleep( and replace it with the string sleep(.000 this way when sqlmap tries to sleep(1) it will end up doing sleep(.0001) and because of the sleep amplification this will end up sleeping much closer to the amount of time sqlmap is actually expecting.

It’s much easier to do this with a tamper script than with burp. I forget how, but one day I ended up opening /usr/share/sqlmap/tamper/apostrophenullencode.py on my kali linux system. This tamper script does a simple find and replace so it’s a perfect example to work from. Since I never remember where the tamper scripts are supposed to live, whenever I need one I just do a locate nullencode and then just make a copy of the file to edit as my new tamper script.

So here I give you sleepfuck.py:

#!/usr/bin/env python

from lib.core.enums import PRIORITY

__priority__ = PRIORITY.LOWEST

def dependencies():
    pass

def tamper(payload, **kwargs):
    """
    Replaces 'sleep(' with 'sleep(.000' to deal with injections that amplify the sleep time. 
	Try adjusting the number of decimal places as needed.

    console# tamper("sleep(1)")
    'sleep(.0001)'
    """
	
    return payload.replace('SLEEP(', "SLEEP(.000") if payload else payload

False Negative

Next is an issue where sqlmap was failing to detect a valid MySQL injection issue. It would initially report it as being OR boolean-based blind - WHERE or HAVING clause (NOT) vulnerable which was accurate, but would then end up reporting it as a false positive or unexploitable which was totally incorrect.

As always, when sqlmap is giving me issues I crank the verbosity to 4 and start looking at what it’s doing. Here’s what it looks like right before it decides that the injection point is unexploitable:

Next I opened a MySQL terminal to check if this was valid syntax:

As I suspected this is invalid syntax. After consulting with my collegue Dark12, we concluded (though not with complete certainty) that the reason sqlmap called this unexploitable was because the false condition and the invalid syntax condition had the exact same response length. Our solution was to proxy sqlmap through burp while it was doing it’s false positive checks and modify the response length (by adding garbage to the response) when it was doing it’s invalid syntax testing. This worked, and it got through the final stages of validation. From there¬† I was able to dump data without having to do any funny business with burp and there were no issues. I imagine that there may be some cases where burp needs to know if it did invalid syntax, (maybe when trying to fingerprint the dbms?) but for this scenario dumping data worked like a charm even though we fudged the invalid syntax results during sqlmap’s initial testing.

WAF Bypass

The last scenario I want to mention in this post is a WAF bypass. The vector was once again boolean based blind MySQL injection. The first issue I encountered was that the ORD() function was blocked. ORD() takes a string as input and returns the character code for whatever the leftmost character of that string happens to be. I noticed however, that the ASCII() keyword was not blocked. ASCII() does the same thing as ORD(), but only operates on ASCII characters, whereas ORD() will work on binary data as well as ASCII. As long as I was dumping text this most likely not going to be an issue, so quickly I created ord2ascii.py, which is the same find and replace script shown above, but swapping ORD( for ASCII(. This was good enough for me to be able to dump the current database user, but as I tried making more complex queries (trying to retrieve user credential info), I started hitting the WAF again.

So in my continued efforts to beat the WAF I discovered the space2hash tamper script, which is built in to kali. The script simply takes a space and replaces it with %23RandomString%0a where of course, %23 is a # and %0a is a newline (\n). Here you can see the relevant code:

#!/usr/bin/env python

...

    # random.seed(0)
    # tamper('1 AND 9227=9227')
    '1%23nVNaVoPYeva%0AAND%23ngNvzqu%0A9227=9227'
    """

    retVal = ""

    if payload:
        for i in xrange(len(payload)):
            if payload[i].isspace():
                randomStr = ''.join(random.choice(string.ascii_uppercase + string.ascii_lowercase) for _ in xrange(random.randint(6, 12)))
                retVal += "%%23%s%%0A" % randomStr
            elif payload[i] == '#' or payload[i:i + 3] == '-- ':
                retVal += payload[i:]
                break
            else:
                retVal += payload[i]

    return retVal

I noticed that space2hash was helping me around some parts of the WAF, but wasn’t quite good enough. At some point I tried doing ORD(%23RandomString%0aSTRING)which was simulating the process of adding a space to the inside of the ORD() function argument and then running the space2hash tamper script. This prompted me to create parenspace.py (parenthesis + space) which, in combination with space2hash, allowed me to completely bypass the WAF. The code for parenspace is another simple search and replace, except it’s looking for ( and which gets replaced with (¬† . Note, when combining these scripts it’s important for the parenspace to run before space2hash runs.

With those two tamper scripts together, a query like this:

ends up looking like this:

That’s it for now. Hope this helps someone! This is just a sampling of some of the weird things I’ve had to do with sqlamp recently. Over the years there have been quite a few interesting challenges. I’ll do my best to continue documenting them as I go forward.