Time-Based Blind SQL Injection: Reading a Database Through the Clock
When two responses are byte-for-byte identical, the database can still answer questions — by stalling. Time-based blind SQLi reads data purely from how long a request takes.
IF(1=1,SLEEP(2),0)At a glance
CWE-89- Interpreter
- SQL database engine and application clock
- Required condition
- An injectable condition can trigger a measurable delay in an otherwise identical response.
- Potential impact
- Blind extraction through timing alone, with no visible data or error required.
- Primary defense
- Parameterized queries; timeouts limit harm but do not remove the vulnerability.
Safe practice: use benign proof only, in a disposable local lab or on a system you are explicitly authorized to test.
On this page
Boolean-based blind injection needs some observable difference between a true and a false condition — a different status code, a different length, a different word on the page. Sometimes there is none. The endpoint swallows errors, returns a fixed 200 OK with an identical body whatever you send, and exposes no boolean to read. The query still runs, though, and that is enough.
Time-based blind SQL injection extracts data from the one channel that is almost impossible to hide: the response time. By making the database pause only when a condition is true, the attacker turns a stopwatch into a data reader. This post covers conditional delays across engines, why this is the technique of last resort, and the reliability concerns that make it tricky to get right.
When this technique applies
Time-based confirmation is useful only when the response body, status and other observable behavior do not provide a cleaner signal. It requires an injectable expression, a database delay primitive reachable in that context, and a delay that stands out from ordinary latency without creating operational harm.
A single slow request proves nothing. Queueing, cold starts, rate limits, garbage collection and database locks all create delays. A valid result needs a baseline plus repeated true and false controls whose distributions remain clearly separated.
How it happens
The vulnerable code looks like any other injection, but it is wrapped so nothing about the result reaches the client. A tracking endpoint that always returns the same fixed body is a perfect example:
$ref = $_GET['ref']; $db->query("INSERT INTO visits (ref, ts) VALUES ('$ref', NOW())"); http_response_code(200); echo "ok";
The response is always 200 ok. There is no row to display, no error, no length change — but the INSERT executes the injected SQL all the same. With no boolean to observe, the attacker introduces a delay they can observe.
A concrete attack: conditional delays
The core trick is a conditional sleep: pause for several seconds if a condition holds, return instantly otherwise. The exact function depends on the engine.
On MySQL, SLEEP inside an IF gives a clean conditional delay using a fixed condition:
' AND IF(1=1, SLEEP(2), 0) -- -
On SQL Server, WAITFOR DELAY stalls the batch for a fixed interval:
'; IF (1=1) WAITFOR DELAY '0:0:2' -- -
On PostgreSQL, pg_sleep is wrapped in a CASE:
' AND (SELECT CASE WHEN 1=1 THEN pg_sleep(2) ELSE pg_sleep(0) END) IS NOT NULL -- -
In each case the safe proof asks only whether a constant expression is true. Pair it with 1=2, which should not delay. The answer is “did the true control take about two seconds longer than the false control?” rather than “did the page change?” This confirms conditional execution without selecting private data.
When the response body is byte-for-byte identical no matter what you inject, the clock is the only thing left to read — and the database will happily tell the time.
Why it matters
Time-based injection is the technique of last resort, and that is precisely why it is dangerous: it works against targets that look silent. An endpoint that returns a constant 200 ok, leaks no errors and renders no query output may still evaluate attacker-controlled conditions. Repeated conditions could infer data, but an authorized assessment should stop after the fixed true/false proof.
Reliability and thresholds
Timing is noisy. Network jitter, server load and connection pooling all add variance, so a naive "took longer than 5 seconds" check produces false positives. Robust extraction defends against this:
- Calibrate a baseline. Measure normal latency first, then choose the shortest delay that remains well above the noise floor.
- Confirm, don't guess. Re-issue the request; a real injection delays consistently, jitter does not.
- Mind connection limits.
SLEEPholds a database connection for its duration; firing many in parallel can exhaust the pool and skew every measurement.
Some engines need stacked-query support for WAITFOR, and stored-procedure or driver settings can suppress it. Rate limits, asynchronous jobs and reverse-proxy timeouts can also hide or distort the signal.
Safe verification boundaries
- Use a disposable lab or an explicitly authorized staging target.
- Prefer a two-second delay over long sleeps when latency makes it distinguishable.
- Send requests serially so the test does not exhaust the connection pool.
- Compare repeated baseline, true and false groups rather than one pair.
- Stop after conditional execution is established; do not infer production values.
The fix: parameterize and cap query time
The same defense applies — bind the input so it can never become a sleeping predicate:
using var cmd = new SqlCommand( "INSERT INTO visits (ref, ts) VALUES (@ref, SYSUTCDATETIME())", connection); cmd.Parameters.Add("@ref", SqlDbType.NVarChar, 256).Value = ref;
Defense in depth helps too: enforce a server-side statement timeout so no single query can hang for seconds, and run under a least-privilege account. A timeout limits resource consumption and may blunt the timing channel, but it is not the primary fix: a shorter conditional computation may still leak a difference until parameterization removes the injection.
How SelfSec finds it
SelfSec's SQL injection engine covers 19 database families and uses time-based probing alongside error-based, boolean, UNION, stacked and out-of-band techniques. It calibrates baseline latency, sends engine-correct conditional delays such as SLEEP, WAITFOR DELAY and pg_sleep, and requires repeated measurements that remain distinct from the no-delay control. Each result includes a reproduction request. Core scan processing runs locally on 127.0.0.1; managed OOB confirmation uses the documented remote callback flow when enabled.
References
SelfSec is intended strictly for authorized security testing of systems you own or are explicitly permitted to assess.
Do both things about SQL Injection
SelfSec covers this class from both sides — the scanner confirms it in your own app, the firewall blocks it in front of your origin while the fix ships.