SQL injection
classBreaking out of a string or numeric context so the database runs your syntax instead of treating it as data.
01Detect
Make the app behave differently. You are not exploiting anything yet.
Cheapest thing first, one character at a time, into every parameter, header and cookie — not just the ones that look like IDs. Sort order, page size, language cookies and X-Forwarded-For all end up in queries.
1, the value is being evaluated as maths, not a string.What a hit looks like — a SQL error string in the body, a 500 where you had a 200, a blank response, or the same 200 with a different Content-Length. That last one is the most commonly missed and the most common in practice. Diff the response sizes, don't just eyeball the page.
02Confirm with boolean logic
Prove you control the WHERE clause, not just that something crashed.
An error only tells you your input reached the database badly. A boolean pair tells you the database is evaluating what you send. Always send both halves and compare.
-- - form survives trailing-space stripping where a bare -- does not.Identical responses mean it is not injectable here. Move to the next parameter rather than escalating. Most wasted time on SQLi goes into escalating against a parameter that never confirmed in the first place.
03Fingerprint the database
Everything past this point is engine-specific. Guessing costs hours.
Concatenation syntax is the fastest tell because it differs on every major engine and fails loudly when wrong.
|| is logical OR by default, so it returns 0 rather than a string.| MySQL | # or -- — the second needs a trailing space or control character |
| PostgreSQL | --, and stacked queries are allowed |
| MSSQL | --, stacking allowed, which is why xp_ procedures are reachable |
| Oracle | --, no ; stacking — everything has to fit in one statement |
MySQL versioned comments do double duty. /*!50000SELECT*/ executes on MySQL 5.0 and above and is an ordinary comment to every other parser. If a payload works with it and fails without, you have confirmed MySQL and confirmed a filter sits in front of it.
04Find the extraction route
Four routes, in descending order of how much you'll enjoy them.
Oracle needs a FROM on every SELECT — use FROM dual when you have no real table. This is the single most common reason a copied Oracle payload fails.
05Blind and out-of-band
No output, no error, no timing difference? Make the database call you.
Out-of-band beats time-based whenever the database can reach the network. It is faster, it survives jitter, and one DNS hit is unambiguous proof where a five-second delay is arguable.
Baseline before you believe it. Send the same payload with a zero-second delay and time it. An endpoint that already takes four seconds will happily convince you it is injectable when it is not.
06When a filter is in the way
Nothing here is worth trying until you have a payload that works without the filter.
Get it working somewhere unfiltered first — a local instance, a different parameter, a test endpoint — then transform it. Blind-firing transformed payloads at a filtered endpoint produces results you cannot interpret.
admin — the word never appears in the request.The full treatment of encodings, decode chains and parser differentials is on the encoding page. What's here is only the SQL-specific subset.
Two you will see in old write-ups that no longer work. The GBK charset squeeze — %bf%27 surviving escaping to become a valid multibyte character plus a live quote — needed the connection charset to be multibyte. Modern deployments default to utf8mb4, where it does nothing. And ' OR 1=1/* with an unterminated block comment is rejected by current parsers rather than swallowing the rest of the statement. If a recent article presents either as live, it was copied from an old one.