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
-- 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
| Engine | Input 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 |
| MySQL | CONCAT('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
' 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 fastFingerprinting 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.
Related
Why string-concatenated queries break: the database never sees your intent, only a finished string it must parse as code.
Where your input lands in the query determines which payloads can possibly work. Get the context wrong and every payload fails for the wrong reason.
Manufacturing your own signal when the application reveals nothing at all: make the database pause, and read the answer from the clock.
Extracting data one bit at a time by asking yes/no questions and reading the answer from how the page changes.
A repeatable order of operations for finding and confirming SQL injection: map the surface, probe every parameter, fingerprint, extract the minimum to prove impact.
sqlmap in practice — techniques, tamper scripts, escalation flags — plus Burp, and when to write your own extraction script instead.