Blind SQL Injection

Chapter 5
Blind SQL Injection
Boolean & time-based inference when there's no visible output

Union extraction (Chapter 4) needs the app to show query results. Often it doesn't — a login that just says "success" or "failure," a search that returns a generic page. The query is still injectable, but you can't see the answer. Blind SQLi recovers the data anyway, by turning the application into a yes/no oracle and reading one bit at a time. It's slower than union-based, but no less complete — and fully automatable. (Defensive framing: own systems / training labs only.)

The Core Idea: The App as a Boolean Oracle

You can't see data, but you can ask the database a true/false question and observe whether the app behaves differently. If you can reliably distinguish "the condition was true" from "the condition was false," you can ask questions about the data and reconstruct it bit by bit. The two ways to get that true/false signal define the two sub-types.

Boolean-Based Blind

Here the page content differs (even subtly) between a true and a false condition — different text, different length, present-or-absent results, a 200 vs a 500. First establish the tell:

' AND 1=1-- // TRUE → page renders normally / "Welcome back" ' AND 1=2-- // FALSE → page differs / "Invalid" / no results

Now swap the constant for a question about the actual data, and read the answer from which page you get back:

// is the 1st character of the admin password greater than 'm' (ASCII 109)? ' AND ASCII(SUBSTRING((SELECT password FROM users WHERE username='admin'),1,1)) > 109-- // TRUE-page → yes, char code > 109 // FALSE-page → no, char code <= 109

Binary Search: ~7 Requests per Character

Testing every possible character one by one is wasteful. Instead use a binary search on each character's ASCII value — each request halves the remaining range, so a full byte (0–127 printable, really ~95 candidates) is pinned in about 7 requests:

// narrowing the 1st character's ASCII code (binary search): > 64 ? TRUE // it's in 65–127 > 96 ? TRUE // 97–127 (lowercase range) > 112 ? FALSE // 97–112 > 104 ? TRUE // 105–112 > 108 ? TRUE // 109–112 > 110 ? FALSE // 109–110 = 109 ? TRUE // char code 109 = 'm'

Then advance to position 2 (SUBSTRING(...,2,1)), repeat, and you've reconstructed the whole password. You typically first extract the length (' AND LENGTH(password)=N) so you know when to stop.

Time-Based Blind

Sometimes the page is identical for true and false — no content difference at all. Then you manufacture your own signal: make the database pause when the condition is true, and read the answer from the response time:

DatabaseConditional delay
MySQL' AND IF(condition, SLEEP(5), 0)--
PostgreSQL' AND (CASE WHEN condition THEN pg_sleep(5) ELSE pg_sleep(0) END)--
SQL Server'; IF (condition) WAITFOR DELAY '0:0:5'--
// same question, but answered by a delay instead of page content: ' AND IF(ASCII(SUBSTRING((SELECT password ...),1,1)) > 109, SLEEP(5), 0)-- // response takes ~5s longer → condition TRUE; returns instantly → FALSE
Time-based is the universal fallback — and why "it returns nothing useful" is no defence
Boolean-blind needs a detectable content difference; time-based needs none — it works even when the response is byte-for-byte identical for true and false, because the signal is the clock, not the page. That makes it the technique of last resort that almost always works on an injectable query: as long as your injected SQL executes, you can make it sleep. This is why "the endpoint doesn't display anything" provides no real protection — the data still leaks through timing. (Caveat for testers: timing is noisy — network jitter, load — so real tools use longer/repeated delays and statistics to be sure.)

Why Blind Is Slow but Not Safe

The contrast with union-based is stark and worth internalizing:

Union-basedBlind
Data per requestwhole values / rows / tablesone bit (true/false)
Speedfast (seconds)slow (1 char ≈ 7 requests; +delay for time-based)
Needs visible output?yesno (content) / none at all (timing)
Recovers the same data?yesyes — eventually, completely
"Slow" ≠ "safe" — automation makes thousands of requests trivial
A human extracting a 32-character hash one binary-searched character at a time would make ~224 requests — tedious by hand, but automated tools do it in moments. sqlmap (Chapter 10) drives boolean and time-based blind extraction automatically, issuing the thousands of inference requests and reassembling the data with no manual effort. So the slowness of blind SQLi protects nobody in practice; the data comes out just the same. The lesson repeats: don't rely on hiding output — only parameterized queries (Chapter 7) remove the injectable oracle entirely, so there's no true/false to read and no delay to time.

Hands-On Exercises

Exercise 1

Explain how a tester first establishes a boolean oracle (the true/false tell) on a page with no visible data, then walk a binary search extracting the first character of a secret, showing why it takes ~7 requests rather than ~95.

📄 View solution
Exercise 2

Explain when you must use time-based instead of boolean-based blind, and write a MySQL time-based payload that tests whether the first character of the admin password is 'a'. Explain how the answer is read, and one reason time-based is noisier/less reliable.

📄 View solution
Exercise 3

A developer argues "our login only returns success/failure and shows no errors, so SQLi can't leak data." Rebut this using blind SQLi: explain how data still leaks with no output, why time-based works even on identical responses, and why only parameterization actually closes it.

📄 View solution

Chapter 5 Quick Reference

  • Blind SQLi — no visible output; turn the app into a true/false oracle and extract data one bit at a time
  • Boolean-based — page content differs on true vs false (' AND 1=1 vs ' AND 1=2); ask data questions and read the page
  • Binary search on each character's ASCII (> 109?) → ~7 requests/char; grab LENGTH() first to know when to stop
  • Time-based — make the DB pause on true (SLEEP/pg_sleep/WAITFOR DELAY); read the answer from response time
  • Time-based works even when responses are byte-for-byte identical — the universal fallback; only needs your SQL to execute
  • Blind recovers the same data as union, just slower (one bit/request); “slow” ≠ “safe” — sqlmap automates it
  • Hiding output/errors only forces boolean → time-based; it never removes the injectable oracle
  • Only parameterized queries (Chapter 7) eliminate the oracle — no true/false to read, no delay to time
  • Next chapter: beyond SELECT — INSERT/UPDATE/DELETE, stacked queries, and second-order SQLi