Lab: Blind SQL injection with time delays and information retrieval
Beginner-friendly, step-by-step walkthrough: exploit a blind SQL injection using conditional time delays (pg_sleep) via a TrackingId cookie. Includes exact payloads (placeholders), Burp Repeater & Intruder setup, optimizations, troubleshooting, detection recipes and developer remediation.
⚠️ Disclaimer
This write-up is for educational and defensive purposes only. Perform these techniques only in legal, controlled environments such as PortSwigger Web Security Academy or your own authorized testbeds. Do not use these techniques on systems you do not own or have explicit permission to test.
TL;DR
This lab contains a time-based blind SQL injection where the application executes a SQL query synchronously using the value of a TrackingId cookie. The app doesn’t return query results or errors, and pages look the same - however, we can cause the server to delay its response conditionally (using pg_sleep) and measure response time to learn truths about the database. Using this oracle I discovered the administrator password one character at a time and logged in.
Key payload pattern (readable):
URL-encoded example (ready for Repeater):
This guide shows why it works, how I set Burp Repeater and Burp Intruder, optimizations (binary search, ASCII ranges), troubleshooting, detection ideas, and a developer remediation checklist.
1 - Lab goal & mental model
Lab goal (PortSwigger): Use a time-delay blind SQLi against the TrackingId cookie to recover the administrator password, then log in as administrator.
Mental model: The server runs a query that includes your cookie value and responds only after that query completes. If you can make the query sleep for N seconds only when a condition is true, then the response time becomes an oracle: slow = true, fast = false. With this you can test existence, password length, and each character bit by bit.
Why this matters in real life: many production apps hide errors and results. Timing attacks are low-noise and often overlooked, yet they leak high-value data.
2 - Recon: find the injection point
I loaded the lab with Burp Proxy enabled and looked in Proxy → HTTP history. The front-page request contained a cookie header:
Because TrackingId is user-controllable, it’s a likely candidate. I sent that request to Repeater to experiment.

3 - Confirming a time-based oracle (basic tests)
First, test whether the server will sleep/slow for a crafted payload. In Repeater I used a payload that forces a 10-second delay when the condition is true:
Human-readable (for clarity):
URL-encoded (safe to paste into cookie header):
After sending this request, I measured the response time - roughly 10,000 ms (10 seconds). Then I sent the payload with 1=2 and observed an immediate response. This confirms a timing oracle exists.
Beginner tip: Use a noticeably large sleep (8–10s) when testing, so network jitter doesn’t cause false positives. Lower it for speed once confirmed.

4 - Confirming target row & simple existence checks
Next I verified the users table and the administrator row exist:
This caused a 10s delay - the condition is true. So: users table exists and contains administrator.

5 - Discovering password length
To bound extraction we first determine password length. I used Repeater and sent multiple requests testing LENGTH(password) > N:
I incremented N until the delay stopped - the last N where delay occurred indicates length > N. In this lab the password length was 20. For real tests prefer a binary search (test >8, >16, etc.) to reduce requests.
Binary search tip: if you suspect length ≤ 40, binary search finds it in ~6 requests instead of 20.

6 - Character extraction strategy (overview)
With the length known, extract each character by testing SUBSTRING(password, pos, 1) = 'x' for all possible characters. PortSwigger hints lowercase alphanumeric (a–z,0–9) - 36 possibilities per position.
Character test (URL-encoded for cookie header):
If response time ~10s → character = 'a'. Repeat for all characters and positions. Doing this manually is slow, so we use Burp Intruder.

7 - Burp Intruder: precise setup for timing attacks
Automation is essential. Here is the step-by-step Intruder setup I used (and recommend):
- Send request to Intruder: In Repeater after verifying a single test, right-click → Send to Intruder.
- Positions tab: In the
TrackingIdcookie value, replace the tested character with§a§and set the position number (pos) manually in the payload string (or use two markers if you want to cluster-bomb positions & chars). Example cookie value for position 1:
- Payload type: Choose Simple list and paste
a–zthen0–9. - Resource pool (single-threaded): This is critical. Timing attacks need reliable timing; use a resource pool with Maximum concurrent requests = 1 (Resource pool → Add → limit 1). Assign the attack to this pool.
- Attack settings: Low concurrency, a short timeout slightly higher than your sleep (e.g., 15s if sleep=10s).
- Launch: Start the attack. Watch the Response received or Time (ms) column. A value ~10,000 ms signals a hit.
- Iterate: Change the
1to2for the second character and repeat until all positions done.
Why single-threaded? Parallel requests can overlap and skew timing measurements. Running one at a time gives clean timing results.


8 - Optimizations (speed & reliability)
- Binary search on character code: instead of testing 36 values per position, test ranges using
ASCII(SUBSTRING(...)) > Xto cut tests to ~6 per position. Example:
- Reduce sleep time after confirm: After a couple of successful tests, drop sleep to 4–6s to speed up extraction (but keep enough headroom vs network jitter).
- Use a smaller charset if confirmed: If you discover character distribution (only lowercase), don’t test uppercase symbols.
- Retry logic: For any ambiguous timing (near threshold), retry the single test a couple of times to avoid false positives.
9 - Troubleshooting common issues
| Symptom | Cause | Fix |
|---|---|---|
| No measurable delay | Payload not reaching DB or pg_sleep blocked | Verify payload executes in Repeater; try different injection syntax or check for filtering |
| Small timing variance around threshold | Network jitter or parallel requests | Increase sleep to 10s for tests; use resource pool = 1 |
| WAF / IDS blocking | Defense detected high latency patterns | Slow down requests, coordinate with ops for authorized tests |
| SUBSTRING not supported | Different DB flavor/func | Use POSITION/SUBSTR or DB-specific functions (adapt syntax) |
| Too slow overall | Large length and charset | Use binary search or ASCII range method |
Always verify a single position manually in Repeater before running a full Intruder attack.
10 - Logging in & verification
After assembling the full password from all positions, I used a standard login POST to confirm the credentials:
Success = lab solved (lab shows admin page / congratulations). In public reports, redact the actual password and only provide redacted evidence.


11 - Defensive perspective (how to stop timing attacks)
This is the essential section for devs & ops - actionable fixes:
- Parameterized queries: The definitive fix - never interpolate cookie values into SQL. Use prepared statements/bind parameters.
- Input validation / whitelist: For
TrackingId, accept only expected formats (UUID, signed token) and reject characters like',;,--. - Least privilege DB account: The web front-end user should not be able to read sensitive tables (users).
- Consistent responses: Avoid conditional response behavior that varies in time or content based on queries. If possible, return quickly and handle long ops asynchronously.
- WAF / rate limits: Detect repeated timing probes and throttle or block. Monitor for repeated cookie permutations.
- Monitoring: Alert on unusual latency patterns correlated with suspicious parameter values.
- Testing: Add automated timing-probe tests in staging and fail pipelines if time-based or boolean oracles appear.
12 - Real-world impact & case studies
- Quiet exfiltration: Attackers can extract credentials slowly over days, staying under detection thresholds.
- Pivoting: Retrieved credentials often allow access to internal tooling or admin areas, enabling further compromise.
- Mitigation example: A SaaS company fixed a timing-based leak by parameterizing queries, rotating credentials, and throttling requests - this stopped an ongoing low-volume exfil attempt.
These show why even “low-bandwidth” timing oracles are high-risk.
13 - Reporting & remediation checklist (copy-paste)
- Replace dynamic SQL in endpoints/cookies with parameterized queries.
- Validate and canonicalize cookie formats.
- Restrict DB user privileges for the web app.
- Implement WAF rules to identify timing probes (pg_sleep, SLEEP, WAITFOR).
- Add staging tests that probe boolean/time-based SQLi.
- Redact DB errors from public responses and log them safely server-side.
14 - Final thoughts
Time-based blind SQLi is methodical but straightforward. The lab teaches patience, careful measurement, and how small control over execution flow (a sleep) becomes a powerful oracle. The fixes are simple and should be prioritized: parameterize, validate, limit privileges, and monitor.
References
- PortSwigger Web Security Academy - Time-based blind SQL injection labs.
- OWASP - SQL Injection Prevention Cheat Sheet.
- Burp Suite docs - Intruder resource pools and timing measurement.