Back to Tutorials
SecurityIntermediate
PHP Security: Preventing SQL Injection
Master PDO prepared statements and other essential security practices for database interaction.
Maria Garcia
January 2, 2026
30 min read
PHP Security: Preventing SQL Injection
SQL injection is one of the most dangerous web vulnerabilities. Learn how to protect your applications using modern PHP security practices.
Understanding SQL Injection
The Vulnerability
php
1// DANGEROUS - Never do this!
2$id = $_GET['id'];
3$query = "SELECT * FROM users WHERE id = $id";
4$result = mysqli_query($conn, $query);An attacker could send: ?id=1 OR 1=1 and retrieve all users!
The Attack Vector
sql
1-- Original query
2SELECT * FROM users WHERE id = 1
3
4-- Injected query
5SELECT * FROM users WHERE id = 1 OR 1=1
6
7-- Result: Returns ALL users instead of one!Using PDO Prepared Statements
Correct Approach
php
1$pdo = new PDO('mysql:host=localhost;dbname=myapp', 'user', 'pass');
2$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
3
4$id = $_GET['id'];
5$stmt = $pdo->prepare('SELECT * FROM users WHERE id = ?');
6$stmt->execute([$id]);
7$user = $stmt->fetch(PDO::FETCH_ASSOC);Named Parameters
php
1$stmt = $pdo->prepare('SELECT * FROM users WHERE email = :email AND status = :status');
2$stmt->execute([
3 ':email' => $email,
4 ':status' => 'active'
5]);
6$user = $stmt->fetch();Building a Secure Database Class
php
1class Database
2{
3 private PDO $pdo;
4
5 public function __construct(string $dsn, string $user, string $pass)
6 {
7 $this->pdo = new PDO($dsn, $user, $pass, [
8 PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
9 PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
10 PDO::ATTR_EMULATE_PREPARES => false,
11 ]);
12 }
13
14 public function query(string $sql, array $params = []): array
15 {
16 $stmt = $this->pdo->prepare($sql);
17 $stmt->execute($params);
18 return $stmt->fetchAll();
19 }
20
21 public function execute(string $sql, array $params = []): bool
22 {
23 $stmt = $this->pdo->prepare($sql);
24 return $stmt->execute($params);
25 }
26
27 public function lastInsertId(): string
28 {
29 return $this->pdo->lastInsertId();
30 }
31}Practical Examples
User Registration
php
1function registerUser(Database $db, string $email, string $password): bool
2{
3 // Hash password
4 $hashedPassword = password_hash($password, PASSWORD_ARGON2ID);
5
6 // Use prepared statement
7 return $db->execute(
8 'INSERT INTO users (email, password, created_at) VALUES (?, ?, NOW())',
9 [$email, $hashedPassword]
10 );
11}User Login
php
1function loginUser(Database $db, string $email, string $password): ?array
2{
3 $users = $db->query(
4 'SELECT * FROM users WHERE email = ? LIMIT 1',
5 [$email]
6 );
7
8 if (empty($users)) {
9 return null;
10 }
11
12 $user = $users[0];
13
14 if (password_verify($password, $user['password'])) {
15 return $user;
16 }
17
18 return null;
19}Search Functionality
php
1function searchPosts(Database $db, string $keyword): array
2{
3 // Use LIKE with prepared statements
4 $searchTerm = '%' . $keyword . '%';
5
6 return $db->query(
7 'SELECT * FROM posts WHERE title LIKE ? OR content LIKE ?',
8 [$searchTerm, $searchTerm]
9 );
10}Additional Security Measures
Input Validation
php
1function validateEmail(string $email): bool
2{
3 return filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
4}
5
6function sanitizeInput(string $input): string
7{
8 return htmlspecialchars(trim($input), ENT_QUOTES, 'UTF-8');
9}Whitelisting
php
1function getOrderBy(string $column): string
2{
3 $allowed = ['name', 'email', 'created_at'];
4
5 return in_array($column, $allowed) ? $column : 'created_at';
6}
7
8$orderBy = getOrderBy($_GET['sort'] ?? 'created_at');
9$users = $db->query("SELECT * FROM users ORDER BY $orderBy");Common Mistakes to Avoid
1. Don't Trust User Input
php
1// BAD
2$table = $_GET['table'];
3$query = "SELECT * FROM $table";
4
5// GOOD
6$allowedTables = ['users', 'posts', 'comments'];
7$table = in_array($_GET['table'], $allowedTables) ? $_GET['table'] : 'users';2. Don't Use String Concatenation
php
1// BAD
2$query = "SELECT * FROM users WHERE id = " . $id;
3
4// GOOD
5$stmt = $pdo->prepare('SELECT * FROM users WHERE id = ?');
6$stmt->execute([$id]);3. Always Use Parameterized Queries
Even for "safe" data, use prepared statements for consistency and safety.
Security Checklist
Conclusion
SQL injection is preventable with proper coding practices. Always use prepared statements, validate input, and follow the principle of least privilege for database users.