SQL InjectionTime-Based Blind

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.

SelfSec Team5 min read
Part 7 of 8from theSQL Injectionseries
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. SLEEP holds 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.

The SQL Injection series

  1. 01OverviewSQL Injection, Explained: From Login Bypass to Full Data Extraction6 min
  2. 02Boolean-Based BlindBoolean-Based Blind SQL Injection: Extracting Data One Bit at a Time4 min
  3. 03Error-BasedError-Based SQL Injection: Turning Database Errors Into Data4 min
  4. 04Out-of-BandOut-of-Band SQL Injection: Confirming a Blind Flaw Through DNS5 min
  5. 05Second-OrderSecond-Order SQL Injection: When Stored Input Detonates Later4 min
  6. 06Stacked QueriesStacked Queries: Running Multiple Statements Through One Injection4 min
  7. 07Time-Based BlindTime-Based Blind SQL Injection: Reading a Database Through the ClockReading
  8. 08UNION-BasedUNION-Based SQL Injection: Appending Your Own Result Set4 min

Related reading