Skip to content
CWE-89A03:2021 – Injection

Detection

Confirming an injection point exists, distinguishing it from ordinary input validation errors, and fingerprinting the engine before you commit to a technique.

Confirm Before You Exploit

A 500 error after sending a quote is suggestive, not conclusive. Plenty of applications throw on unexpected input for reasons that have nothing to do with SQL. Before investing in exploitation, get a signal that can only be explained by your input being parsed as SQL.

The three reliable confirmations are:

  • Differential response — two inputs that are equivalent as text but different as SQL produce different pages.
  • Arithmetic evaluation — the database computes something for you.
  • Controlled delay — the response time moves when and only when you ask it to.

The third proves execution even when the application shows you nothing at all.

Differential Testing

The strongest evidence is a matched pair: one payload that is logically true, one that is logically false, identical in every other respect. If true renders the normal page and false renders something else, the database is evaluating your logic.

Use a pair that survives WAFs and does not depend on knowing the schema:

True/false pair

Payload
-- String context
' AND '1'='1     -> normal page
' AND '1'='2     -> different page / no results

-- Numeric context
 AND 1=1         -> normal page
 AND 1=2         -> different page / no results

-- Both members are syntactically valid. If only one changes the output,
-- the difference is semantic, which means SQL evaluated it.

Arithmetic Probes

For numeric parameters, replace the value with an expression that evaluates to it. If id=4+1 returns the same record as id=5, the database performed the addition — the parameter is unquoted and injectable.

This is a clean test because it produces no error, trips no WAF signature, and has no false-positive explanation. A parameter that merely tolerates 4+1 while returning nothing tells you little; one that returns record 5 is conclusive.

For string contexts the equivalent is concatenation, which is engine-specific and doubles as a fingerprint:

Concatenation as a fingerprint

EngineInput for value 'alice'Confirms
MySQL'ali' 'ce'Adjacent string literals concatenate — MySQL only
PostgreSQL / Oracle / SQLite'ali'||'ce'Pipe concatenation
MSSQL'ali'+'ce'Plus concatenation
MySQLCONCAT('ali','ce')Works on MySQL and PostgreSQL

Timing Probes

When nothing about the response changes — no errors, no differential, no reflected data — timing is the fallback that still proves execution.

Send a conditional delay. If a response that normally takes 80 ms takes 5.1 s exactly when your condition is true, your SQL ran. Always test the negative case too: a payload with a false condition must return at normal speed. A delay that happens regardless of the condition is a slow endpoint, not an injection.

Per-engine delay probes

SQL
' AND SLEEP(5)-- -

-- Conditional form, so you can prove the negative case too:
' AND IF(1=1,SLEEP(5),0)-- -
' AND IF(1=2,SLEEP(5),0)-- -   # must return fast

Fingerprinting the Engine

Everything past this point diverges by engine. Identify it early.

If errors are visible, the error text usually names the product outright (ORA-01756, You have an error in your SQL syntax, Unclosed quotation mark, PG::SyntaxError). If not, the concatenation table above distinguishes the families in one request each, and comment syntax narrows it further: # is accepted as a comment by MySQL and rejected by everything else.

The Cheatsheet has the full per-engine matrix; the DBMS Reference guides cover each in depth.

Ruling Out False Positives

Treat these with suspicion:

  • A 500 on any unusual character. Test with <, backtick, and a long random string. If everything errors, the app is just fragile.
  • A differential that does not reproduce. Re-run both members several times. Caching, A/B tests, and rate limits all produce differences that have nothing to do with your payload.
  • A delay you did not control. Vary the sleep duration — 2 s, then 10 s. The response time must track it. If it does not, you found a slow query, not an injection.
  • WAF-induced differences. A blocked request often returns a distinct page. That is the WAF answering, not the database. Compare against a payload that is equally suspicious but semantically inert.