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.
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
#!/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
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.
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
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
%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
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.