| name | hunt-sqli |
| description | SQL injection hunting methodology across error-based, union, blind boolean, and time-based variants. Use when testing any input that might reach a database. |
Hunting SQL Injection
Detection — one payload at a time
Start minimal. Don't lead with sqlmap; a single well-placed ' tells you more than 10,000 noisy requests.
Fingerprint (inject, observe)
' → 500 / error / shape change
'' → same as original (concatenation proof)
' OR 1=1-- vs ' OR 1=2-- → different response sizes
' AND SLEEP(5)-- (MySQL), '; WAITFOR DELAY '0:0:5'-- (MSSQL), ' || pg_sleep(5)-- (Postgres)
Where to inject
Every parameter, every method, every header that hits the DB — don't forget:
ORDER BY clauses (can't use UNION directly; injection via column name)
LIMIT / OFFSET (numeric; no quotes to break)
LIKE wildcards
- JSON field names (for JSONPath-aware DBs)
- Search filters and "advanced search" builders (often raw SQL concat)
DB fingerprinting
Once you confirm SQLi, identify the DB:
- MySQL:
@@version, /*! */ inline comments
- Postgres:
version(), ::text casts
- MSSQL:
@@VERSION, WAITFOR DELAY
- SQLite:
sqlite_version()
- Oracle:
v$version, FROM DUAL
Exploitation paths (in order of noise)
- Error-based — leak data through forced errors (
EXTRACTVALUE, UPDATEXML, CONVERT)
- Union-based — cleanest; requires knowing column count (
ORDER BY 1,2,3... until error)
- Boolean blind — one bit per request via content length difference
- Time blind — slowest; use when no differential response at all
NoSQL injection (often missed)
MongoDB: {"username": {"$ne": null}, "password": {"$ne": null}} for auth bypass; $regex for extraction; $where for JS eval.
Second-order SQLi
Payload stored in profile / name / comment, triggered later by an admin-side query. Don't just test reflected points — register an account with a payload in a field and visit admin-visible pages.
Filter bypasses
- Comment variations:
-- -, #, /**/
- Case:
SeLeCt
- Whitespace:
/**/, %09, %0a, %0b, %0c, %0d, +
- Keyword splitting:
UN/**/ION SE/**/LECT
- Quoted strings as hex:
0x61646d696e = 'admin'
CHAR() / CHR() for string building
sqlmap — when you've already confirmed manually
sqlmap -r req.txt --dbms=mysql --risk=1 --level=2 --batch
Escalate --level/--risk only if manual testing suggests the injection point needs it. Respect program scanning policy before running at all.
Impact & reporting
- Data read: show a DB version + one row of sensitive data (your own account is fine)
- Data write: rarely needed to prove; describe the capability
- Auth bypass via SQLi: demo login as another account
- RCE via
INTO OUTFILE / xp_cmdshell: Critical — don't exploit, just describe the preconditions
Never dump production tables to prove impact. One row or one version string suffices.