SQL Injection Prevention

JA JASWANT Updated 19 Oct 2026
6 min read ·Lesson 118 of 143
In this post

What Is SQL Injection? In simple terms: SQL injection happens when user input is pasted directly into an SQL query. An attacker can then type carefully crafted text that changes the query itself — log

What Is SQL Injection?

In simple terms: SQL injection happens when user input is pasted directly into an SQL query. An attacker can then type carefully crafted text that changes the query itself — logging in without a password, reading other customers' data, or deleting tables. It is one of the oldest and most damaging web vulnerabilities, and it is completely preventable.

Technically: SQL injection occurs when untrusted data is concatenated into an SQL string, so the database parser cannot distinguish code from data. The fix is to never mix them: send SQL with placeholders and the values separately (prepared statements), use allow-lists for SQL parts that cannot be parameters (identifiers, keywords), and limit the damage any query could do (least privilege).


How Attacks Work

The following examples only print the SQL that vulnerable code would build — they show what the database would receive.

1. Bypassing a Login

<?php
$email = "' OR 1=1 -- ";   // typed into the login form

$sql = "SELECT id FROM users WHERE email = '$email' AND active = 1";
echo $sql . "\n";

Output

SELECT id FROM users WHERE email = '' OR 1=1 -- ' AND active = 1

The quote closes the string; OR 1=1 makes the condition true for every row; -- turns the rest of the query into a comment. The query returns all users, and naive login code logs the attacker in as the first one — often an administrator.

2. Reading Other Tables With UNION

<?php
$search = "x' UNION SELECT email, password_hash FROM users -- ";

$sql = "SELECT name, price FROM products WHERE name LIKE '%$search%'";
echo $sql . "\n";

Output

SELECT name, price FROM products WHERE name LIKE '%x' UNION SELECT email, password_hash FROM users -- %'

The search results page now displays every user's email and password hash in place of product names.

3. Numeric Parameters Without Quotes

<?php
$id = '0 OR 1=1';   // from ?id=0%20OR%201=1

$sql = "DELETE FROM orders WHERE id = $id AND customer_id = 7";
echo $sql . "\n";

Output

DELETE FROM orders WHERE id = 0 OR 1=1 AND customer_id = 7

Because AND binds more tightly than OR, this deletes every order of customer 7 — and with slight variations, every order in the table. Numeric values need no quotes to be dangerous, which is why "escaping quotes" is not a reliable defense.

Other Forms

TypeHow It Works
Blind injectionThe attacker sees no data but infers it from yes/no differences in the page
Time-based injectionPayloads such as SLEEP(5) reveal information through response times
Second-order injectionMalicious input is stored safely, then later inserted unsafely into another query (for example, in an admin report)
Stacked queriesWith multi_query(), an attacker appends entire statements (; DROP TABLE users)

Defense 1: Prepared Statements for Every Value

<?php
require __DIR__ . '/src/pdo.php';

function findActiveUserId(PDO $pdo, string $email): ?int
{
    $stmt = $pdo->prepare('SELECT id FROM users WHERE email = :email AND active = 1');
    $stmt->execute(['email' => $email]);

    $id = $stmt->fetchColumn();
    return $id === false ? null : (int) $id;
}

var_dump(findActiveUserId(pdo(), "' OR 1=1 -- "));

Output

NULL

The database searched for a user whose email is literally ' OR 1=1 -- and found none. With prepared statements, the value is never parsed as SQL — no matter what it contains. This applies to SELECT, INSERT, UPDATE, and DELETE, in PDO (see PDO Prepared Statements) and MySQLi (see MySQL Prepared).

Use placeholders for every value, including:

  • values that "cannot contain anything dangerous" (IDs, numbers, dates),
  • values from your own database (to prevent second-order injection),
  • values from cookies, headers, APIs, and files.

Defense 2: Allow-Lists for Identifiers and Keywords

Placeholders cannot stand for table names, column names, sort directions, or SQL keywords. When these depend on input, map the input to fixed SQL fragments:

<?php
function productOrderBy(?string $sort): string
{
    return match ($sort) {
        'price_asc' => 'price ASC',
        'price_desc' => 'price DESC',
        'newest' => 'created_at DESC',
        default => 'name ASC',
    };
}

echo productOrderBy('price_desc') . "\n";
echo productOrderBy('name; DROP TABLE products') . "\n";

Output

price DESC
name ASC

Input only chooses between fragments written in the code; it never becomes part of the SQL. The same approach applies to column filters, table names in admin tools, and ASC/DESC.

Dynamic Lists and LIKE Patterns

  • For IN (...) lists, generate one placeholder per value (see MySQL Prepared).
  • For LIKE searches, escape the wildcards % and _ in the value and still pass it as a parameter (see MySQL Where).

Defense 3: Least Privilege and Safe Error Handling

Even with perfect code, limit what a compromised query could do:

-- The web application's account: data access only
GRANT SELECT, INSERT, UPDATE, DELETE ON gocourse_shop.* TO 'gocourse_app'@'localhost';

-- A reporting dashboard's account: read-only
GRANT SELECT ON gocourse_shop.* TO 'gocourse_reports'@'localhost';
MeasureEffect
No DROP, ALTER, FILE, or GRANT privileges for the app userInjected statements cannot destroy schemas, read server files, or create users
Separate accounts per application or purposeA breach in one part does not expose everything
Generic error messages; details only in logsAttackers cannot use SQL error messages to map the database
Avoid multi_query() with any external dataStacked queries become impossible

What Is Not a Sufficient Defense

ApproachProblem
addslashes()Does not consider the connection's character set; useless for numeric contexts
mysqli_real_escape_string() / PDO::quote() aloneEasy to forget once; useless for unquoted numbers and identifiers; a source of mistakes
Blocklists ("remove SELECT, UNION, --")Attackers bypass them with encodings, comments, and alternative syntax
Client-side validationAttackers send requests directly
Stored proceduresSafe only if they do not build dynamic SQL from their parameters internally
ORMs and query buildersSafe for their normal methods, but raw query methods (DB::raw(), whereRaw()) are just as dangerous when given input

Finding Vulnerable Code

  • Search the code base for SQL built with variables: query("... $, "WHERE ... " ., sprintf("SELECT, ->whereRaw(.
  • Review every query that uses data not written in the code itself.
  • Use static analysis tools (PHPStan or Psalm with security rules) in your build pipeline.
  • Test your own applications — only with permission — with tools such as sqlmap in a test environment.

Common Mistakes

Prepared Statements With Concatenated Parts

Effect: $pdo->prepare("SELECT * FROM products WHERE category = '$category'") is still vulnerable — preparing a string that already contains input changes nothing.

Fix: Put a placeholder in the SQL and pass the value to execute().

Trusting "Internal" Data

Effect: Second-order injection through stored values.

Fix: Parameterize every value, regardless of its source.

Building ORDER BY From Input

Effect: Injection through sort parameters.

Fix: Allow-list columns and directions.

Displaying Database Errors

Effect: Error messages guide attackers.

Fix: Log errors; show generic messages.


Best Practices

  • Use prepared statements with placeholders for every value in every query.
  • Map dynamic identifiers and keywords to allow-listed SQL fragments.
  • Give the application a database user with minimal privileges.
  • Hide database errors from users and log them for developers.
  • Review code for string-built SQL and test regularly.

Official reference: https://www.php.net/manual/en/security.database.sql-injection.php

0 Comments

Reviewed before they appear

No comments yet.

PHP
PHP Home PHP Switch PHP Match Alternative Syntax PHP Loops While Loop Do While Loop For Loop Foreach Loop Nested Loops Break Statement PHP Home PHP Intro PHP Install PHP Built-in Web Server PHP Syntax PHP Tags PHP and HTML PHP Comments PHP Multiline Comments PHP Variables Variables Scope PHP Casting Logical Operators Null Coalescing Operator Spaceship Operator Operator Precedence Continue Statement Modify Strings PHP If...Else...Elseif PHP If Modify Strings PHP If Operators PHP Functions Concatenate Strings PHP If...Else Slicing Strings User-defined Functions Slicing Strings PHP Shorthand if Escape Characters Default Argument Values PHP Nested if Pass by Reference String Interpolation Named Arguments PHP File Upload Variadic Functions Return Values Search in Strings Multibyte Strings Type Declarations in Functions PHP Numbers PHP Math PHP Floats PHP Random Numbers PHP Constants PHP Properties PHP Methods PHP Constructor Constructor Property Promotion PHP Cookies PHP Destructor PHP Access Modifiers Readonly Properties PHP Sessions PHP Inheritance Method Overriding PHP Filters The final Keyword CSRF Protection PHP Filters Advanced Session Security Secure Cookies PHP Magic Constants File Upload Security PHP Callback Functions PHP Operators Filesystem Security PHP JSON Arithmetic Operators Error Reporting in Production Hiding PHP Assignment Operators PHP Configuration (php.ini) Comparison Operators PHP Command Line (CLI) PHP Exceptions Increment / Decrement Operators Migrating to PHP 8 PHP Form Validation PHP Form Required PHP Form URL/E-mail Error Handling Form Input Sanitizing Escaping Output (htmlspecialchars) Custom Error Handler Checkbox, Radio and Select Inputs Sticky Forms PHP Form Complete PHP Date and Time Date Formatting Error Logging HTTP Headers and Redirects Output Buffering Sending Email PHP cURL Consuming REST APIs PHP Generators AJAX PHP AJAX Database AJAX XML AJAX Live Search AJAX Poll AJAX with JSON Security Introduction Password Hashing SQL Injection Prevention XSS Prevention Garbage Collection Project: Simple Blog PHP What is OOP PHP Classes/Objects The $this Keyword Access Array Items Update Array Items Add Array Items Remove Array Items Sorting Arrays Multidimensional Arrays Array Destructuring Array Spread Operator Search in Arrays Merge Arrays PHP Abstract Classes PHP Interfaces Polymorphism PHP Traits PHP Static Methods PHP Static Properties Late Static Binding Magic Methods Object Cloning
Ask about this post
AI Ask about this post

Ask questions about SQL Injection Prevention and get answers drawn from it.

Signed-in readers only.