Blind SQL injection: boolean vs time-based, and how testers prove it
Blind SQL injection is SQL injection where the application never shows query results or database errors, so the tester infers the answer from a response difference (boolean-based) or from a delay (time-based). Testers prove it with a matched true/false pair or three timing samples against a baseline. The fix is the same as for any SQL injection: parameterized queries.
Blind SQL injection is SQL injection where the application does not return query results or database error text. OWASP describes the technique as one that "asks the database true or false questions and determines the answer based on the applications response." The flaw is the same as in standard SQL injection: user input changes the query. Only the feedback differs.
Blind SQL injection testing is part of web application penetration testing and API penetration testing. Testing needs written authorization from the system owner.
What makes SQL injection "blind"?#
SQL injection is blind when the response carries no query output and no database error, yet the input still reaches the query. The tester learns about the query only from side effects: a changed page, a changed status code, a different length, or a delay.
OWASP says blind SQLi is "more difficult, but not impossible" to exploit than the standard form, and that it differs from standard SQLi only in how data is extracted. For a defender, this means two things. Hiding database errors from users is good practice, but it does not remove the flaw. And logs may show nothing unusual, because each probe is a normal-looking request.
A page can be blind and still be serious. A search box that always shows "N results" and never shows the query is blind. A login that only answers "ok" or "failed" is blind. An API that returns 204 on every call is blind, and the flaw can still sit behind it.
How does boolean-based blind SQL injection work?#
Boolean-based injection compares two responses that differ only in a true and a false condition. If the application reacts differently, the condition reached the query.
Boolean pair against a fictional endpoint
This is a demonstration. Harbor Notes is a fictional app on app.example.com, and no real customer system was tested.
The endpoint GET /api/notes/exists?id= returns {"exists": true} or {"exists": false}. The handler joins the id into its SQL text. A tester sends a baseline and a matched pair, using the true and false conditions OWASP documents:
GET /api/notes/exists?id=2 HTTP/1.1
GET /api/notes/exists?id=2+and+1=1 HTTP/1.1
GET /api/notes/exists?id=2+and+1=2 HTTP/1.1
Host: app.example.com
Results recorded:
| Request | Response | Status |
|---|---|---|
id=2 |
{"exists": true} |
200 |
id=2 and 1=1 |
{"exists": true} |
200 |
id=2 and 1=2 |
{"exists": false} |
200 |
The third request is the same as the second except for one token, and the answer flips. A control with id=2abc returns a 400 validation error, which shows that the endpoint does not treat every odd value the same way. The tester repeats the pair three times with the same result and stops. No data was read.
The pair is the proof. A tester does not need to read any data to show that the input controls the query, and a finding that stops here is complete. Reading further would only add risk.
How does time-based blind SQL injection work?#
Time-based injection makes the database pause when a condition holds, so the response time carries the answer. OWASP says it "works even when the attacker injects the SQL queries and the content of the vulnerable page doesn't change."
Each database engine has its own delay function. The vendor documents describe them as follows.
| Engine | Delay function | What the vendor documentation says |
|---|---|---|
| MySQL | SLEEP(seconds) |
"Sleeps (pauses) for the number of seconds given by the duration argument, then returns 0." The duration can be fractional. |
| PostgreSQL | pg_sleep(seconds) |
The session's process sleeps until the given number of seconds have elapsed. The delay is at least as long as requested and may be longer under load. |
| Microsoft SQL Server | WAITFOR DELAY 'hh:mm:ss' |
Blocks the batch until the time interval elapses. The actual delay depends on server activity. |
Two lines in those documents matter to testers. PostgreSQL says the delay "may be longer under server load," and Microsoft says the delay "depends on the activity level of the server." A busy server produces slow responses that look like injection. For that reason a time-based result needs repeated samples and a baseline.
A tester uses a short delay, two or three seconds, and never a long one. A long delay holds a database connection open and can degrade a live service.
What are out-of-band and error-based techniques?#
When the page does not change and timing is noisy, two other signals can still prove the flaw. Both need care and a clear scope.
| Technique | Signal | When it works | Risk to the target |
|---|---|---|---|
| Boolean-based | Different page, status or length | Response differs for true and false | Low |
| Time-based | Response time | Page identical, timing stable | Low if the delay is short |
| Error-based | Database error text in the response | Errors reach the client | Low |
| Out-of-band | A network request from the database to a host the tester controls | The database can reach the outside network | Medium: needs egress and a listener |
PortSwigger lists out-of-band payloads that "trigger out-of-band network interactions" as one of its standard detection steps. The proof is a timestamped log entry on a listener the tester owns. Out-of-band tests need extra authorization in scope because they make the target system open a connection to a third host.
Is blind SQL injection found in real software?#
Yes. NVD lists many blind SQL injection records. Two examples from WordPress plugins, both published on 2022-03-07, show how ordinary the entry point is.
| CVE | Product | What NVD records |
|---|---|---|
| CVE-2022-0349 | NotificationX WordPress plugin before 2.3.9 | The nx_id parameter is not sanitized and escaped before use in a SQL statement, "leading to an Unauthenticated Blind SQL Injection" |
| CVE-2022-0439 | Email Subscribers & Newsletters plugin before 5.3.2 | The order and orderby parameters are not escaped correctly in the ajax_fetch_report_list action |
CVE-2022-0439 involves order and orderby. Those are the parts of a query that a bound parameter does not cover, which is why sort and filter parameters are a regular source of blind findings. The MOVEit case in our SQL injection guide shows the scale an unauthenticated flaw can reach.
How do penetration testers prove blind SQL injection?#
Testers prove it with controlled, repeatable evidence and read nothing they do not need. The standard has four parts.
- Matched pair. A true and a false request that differ in one token. The response must differ the same way on every repeat.
- Timing samples. Three samples of the baseline and three of the delayed request. The delayed set must be clearly above the baseline set and must track the delay value if it changes from two to three seconds.
- Controls. A nonexistent identifier, a wrong type, an unauthenticated request, and another role. A control that behaves like the exploit means the first result was misleading, so we downgrade or drop the finding.
- Minimal read. Where a read is needed to show impact, one harmless value, such as the database version string. No table dumps. Extraction one character at a time sends many requests, and it adds nothing a pair of requests did not already prove.
Every request is saved with a timestamp. The report labels the result "Observed" when we reproduced it, and lists what we did not test under "Limitations and open questions".
Why do scanners miss blind SQL injection, and how do you confirm a false positive?#
Scanners look for response differences and delays, so they fail where the signal is weak and misreport where it is noisy.
| Cause | Effect | How a tester checks |
|---|---|---|
| Identical page for true and false | Missed finding | Try a timing or out-of-band signal |
| Cached responses | Missed finding or false pair | Add a unique parameter to bypass the cache |
| Slow or busy server | False positive on timing | Take baseline and delayed samples in alternating order |
| Dynamic content such as timestamps | False boolean difference | Compare only the fields that should be stable |
| Rate limiting or a WAF block page | Different response for the wrong reason | Check the block page is not the "false" response |
| Input checked on one path only | Missed finding | Test the same parameter in JSON, form and header form |
To confirm a possible false positive, repeat the pair in a different order, swap the true and false conditions, and run a control that should not change the page. A real flaw follows the condition. A false positive follows the noise.
How do you fix blind SQL injection?#
Fix it the way you fix any SQL injection: parameterized queries. OWASP's cheat sheet lists them as the first defense and says input validation supplements binding and never replaces it. The full code for Java, Python, Node.js, PHP, C#, Go and Ruby is in the SQL injection prevention section.
A short PHP example of the fix for an exists endpoint:
$stmt = $dbh->prepare("SELECT 1 FROM notes WHERE id = ?");
$stmt->execute([(int) $_GET['id']]);
echo json_encode(["exists" => (bool) $stmt->fetchColumn()]);
The PHP manual states that if an application uses prepared statements exclusively, injection through values will not occur, but that any other part of the query built from unescaped input stays injectable. For ORDER BY and column names, map the choice to a fixed list:
$sorts = ['newest' => 'created_at DESC', 'title' => 'title ASC'];
$orderBy = $sorts[$_GET['sort'] ?? ''] ?? $sorts['newest'];
Two further steps apply to blind flaws in particular. Add a per-query statement timeout in the database so a delay probe cannot hold a connection for long. And alert on repeated near-identical requests to one endpoint, since blind probing is noisy even when each request looks normal.
Blind SQL injection compared with standard SQL injection#
| Question | Standard SQL injection | Blind SQL injection |
|---|---|---|
| Does the page show query output or errors? | Yes | No |
| How does the tester learn the result? | Directly from the response | From a response difference, a delay or a callback |
| Is the root cause the same? | String-built query | String-built query |
| Does parameterization fix it? | Yes | Yes |
| Does hiding error messages fix it? | No | No |
| Typical proof | Version string in the response | Matched pair or timing samples |
Blind SQL injection testing checklist#
- List inputs that reach a database, including filters, sort fields, IDs in paths, headers and cookies.
- Record a baseline response three times: status, length, key fields and time.
- Send a true and a false condition that differ in one token. Compare against the baseline.
- If the page never changes, test a short delay of two to three seconds. Take three samples each way.
- Check that the delay moves when you change the delay value.
- Run controls: nonexistent ID, wrong type, unauthenticated request, another role.
- Test
ORDER BYand column parameters separately. - Test out-of-band only if the scope allows it, with a listener you own.
- Stop once the flaw is proven. Read at most one harmless value.
- Save timestamped requests and responses, and list what was not tested.
When to get a professional test#
Blind flaws hide behind clean pages, which is why they survive scans. A tester who reads your endpoints and runs controls will find the ones a scanner misses and rule out the ones it invents. Web application penetration testing from Excession Labs covers injection across forms and endpoints, and API penetration testing covers JSON bodies and filters. We agree the scope in writing first, and the report gives each finding with evidence and fix guidance.
Questions
What is the difference between blind SQL injection and regular SQL injection?
The flaw is the same: input changes the query. The difference is feedback. In regular SQL injection the page shows query output or database errors. In blind SQL injection it shows neither, so the tester reads the effect from a changed page or a delay. OWASP says blind SQLi differs only in how the data is retrieved.
What is boolean-based SQL injection?
Boolean-based SQL injection sends a condition that is true and one that is false, then compares the two responses. If the page, status code or length differs in the same way each time, the input reaches the query. OWASP calls this the content-based form of blind SQL injection.
How do testers detect time-based blind SQL injection?
They send input that makes the database wait for a set time, then compare the response time to a baseline. Because networks vary, a careful tester takes several samples of each and repeats the check. A single slow response is not evidence.
Can a scanner find blind SQL injection?
Often, but not always. Scanners rely on response differences and delays, so noisy servers, caching, and endpoints that return identical pages can hide the flaw or cause false alarms. Confirm every scanner result with a control request.
How do I prevent blind SQL injection?
Use parameterized queries for every value, map table names and sort order to an allow-list, and run the application with a least-privilege database account. Hiding error messages does not fix the flaw, because blind attacks work without error output.
Sources
- OWASP Community: Blind SQL Injection (checked 9 November 2026)
- OWASP SQL Injection Prevention Cheat Sheet (checked 9 November 2026)
- PortSwigger Web Security Academy: SQL injection (checked 9 November 2026)
- CWE-89: Improper Neutralization of Special Elements used in an SQL Command (checked 9 November 2026)
- MySQL 8.4 Reference Manual: Miscellaneous functions (SLEEP) (checked 9 November 2026)
- PostgreSQL documentation: Date/time functions (pg_sleep) (checked 9 November 2026)
- Microsoft Learn: WAITFOR (Transact-SQL) (checked 9 November 2026)
- NVD: CVE-2022-0349 (NotificationX WordPress plugin) (checked 9 November 2026)
- NVD: CVE-2022-0439 (Email Subscribers & Newsletters WordPress plugin) (checked 9 November 2026)
- PHP manual: Prepared statements and stored procedures (checked 9 November 2026)