SQL-инъекции в PHP: как предотвратить атаки на базу данных

Раздел: Базы данных -> Работа с базами данных SQL в PHP

Понимание SQL-инъекций и методы защиты в PHP

SQL-инъекция - это атака, при которой злоумышленник внедряет вредоносный SQL-код в запрос, выполняемый приложением. В PHP такие уязвимости возникают, когда пользовательские данные напрямую конкатенируются в строку запроса. Рассмотрим основные подходы к защите.

Использование подготовленных выражений (PDO)

Как гарантированно защитить запрос от инъекций при любом типе данных?

Наиболее надёжный способ - применение подготовленных выражений через PDO или MySQLi. Запрос и данные передаются раздельно, драйвер автоматически экранирует значения.

$pdo = new PDO('mysql:host=localhost;dbname=test;charset=utf8', 'user', 'pass');
$stmt = $pdo->prepare('SELECT * FROM users WHERE email = :email');
$stmt->execute(['email' => $_POST['email']]);
$user = $stmt->fetch();

Php class sql (класс для работы с sql в php)

Объяснение:

  1. Создаётся соединение с БД через PDO.
  2. Метод prepare разбирает запрос без подстановки данных.
  3. Плейсхолдер :email заменяется значением при выполнении.
  4. Данные обрабатываются как строки, что исключает интерпретацию SQL-команд.

Типичная ошибка: использование PDO::query() с конкатенацией. Если запрос динамически собирается со вставкой переменных, защита теряется.

Проблема: при именованных плейсхолдерах количество плейсхолдеров должно строго соответствовать переданным значениям.

Как защититься с помощью экранирования специальных символов?

Функция mysqli_real_escape_string() экранирует опасные символы, но применима только для строковых значений и не защищает от инъекций в числовых полях, если данные не приведены к типу.

$mysqli = new mysqli('localhost', 'user', 'pass', 'test');
$safe_name = $mysqli->real_escape_string($_POST['name']);
$result = $mysqli->query("SELECT * FROM users WHERE name = '$safe_name'");

Php sql insert (insert в php)

Цель: подходит для устаревших проектов, где нельзя перейти на PDO. Применяется с осторожностью - необходимо оборачивать значение в кавычки.

Ошибка: забыть обернуть экранированную строку в кавычки - запрос станет синтаксически неверным. Нельзя использовать для чисел напрямую (потребуется принудительное приведение типа).

Как фильтровать данные с помощью приведения типов для числовых полей?

Если ожидается целое число, можно привести значение к целому перед вставкой в запрос.

$id = (int)$_GET['id'];
$result = $mysqli->query("SELECT * FROM articles WHERE id = $id");

Php ms sql (работа с ms sql в php)

Используется, когда поле заведомо числовое и нет необходимости в строковых значениях. Подходит для идентификаторов.

Проблема: не работает для строк, дат или других типов. Придётся комбинировать с другими методами.

Как защитить динамический ORDER BY с помощью белого списка?

Для сортировки нельзя использовать подготовленные выражения - имя столбца не может быть плейсхолдером. Решение - проверка по белому списку.

$allowed = ['id', 'title', 'date'];
$order = in_array($_GET['order'], $allowed) ? $_GET['order'] : 'id';
$result = $mysqli->query("SELECT * FROM posts ORDER BY $order");

переменную sql php (использование переменных в sql-запросах php)

Цель: применяется для параметров, которые изменяют структуру запроса (ORDER BY, LIMIT, названия таблиц).

Ошибка: если белый список не содержит все возможные значения, пользователь может получить неверные данные. Нужно всегда задавать значение по умолчанию.

Как использовать ORM для автоматической защиты от инъекций?

ORM (Object-Relational Mapping) библиотеки, такие как Eloquent или Doctrine, сами используют подготовленные выражения.

$user = User::where('email', $_POST['email'])->first();

Подходит для современных фреймворков (Laravel, Symfony). Упрощает код и минимизирует риск человеческой ошибки.

Проблема: ORM добавляет накладные расходы и может быть избыточен для простых скриптов.

- Php sql connect (подключение к sql в php)
- Php database sql (работа с базами данных sql в php)
- Sql where php (условие where в sql-запросах php)

Расширенные примеры SQL-инъекций и защиты

Демонстрация уязвимости и разных техник защиты с полным кодом и результатами.

Пример 1. Инъекция через неэкранированный параметр

Незащищённый код:

Пример
$conn = new mysqli('localhost', 'root', '', 'test');
$email = $_GET['email'];
$sql = "SELECT * FROM users WHERE email = '$email'";
$result = $conn->query($sql);

При передаче email=test@mail.com' OR '1'='1 запрос становится:

SELECT * FROM users WHERE email = 'test@mail.com' OR '1'='1'

Результат: все строки таблицы. Данные скомпрометированы.

Пример 2. Защита через подготовленные выражения (PDO)

Пример
$pdo = new PDO('mysql:host=localhost;dbname=test;charset=utf8', 'root', '');
$stmt = $pdo->prepare('SELECT * FROM users WHERE email = :email');
$stmt->execute([':email' => $_GET['email']]);
$users = $stmt->fetchAll();

Даже если email содержит ' OR '1'='1, запрос остаётся параметризованным - значение целиком сравнивается как строка. Инъекция невозможна.

Пример 3. Использование LIKE с параметрами

Для поиска с LIKE необходимо экранировать символы подстановки или передавать их в параметре:

Пример
$search = '%' . $pdo->quote($_POST['search']) . '%'; // неверно
// Правильно: использовать подготовленное выражение
$stmt = $pdo->prepare('SELECT * FROM products WHERE name LIKE :search');
$stmt->execute([':search' => '%' . $_POST['search'] . '%']);
$results = $stmt->fetchAll();

Пояснение: PDO сам экранирует данные внутри плейсхолдера, символ процента воспринимается буквально, если не является частью строки. Для включения подстановки - добавляем проценты до выполнения.

Пример 4. Динамический IN() с массивом значений

Создание списка плейсхолдеров для каждого элемента массива:

Пример
$ids = [1, 2, 3];
$placeholders = implode(',', array_fill(0, count($ids), '?'));
$stmt = $pdo->prepare("SELECT * FROM orders WHERE id IN ($placeholders)");
$stmt->execute($ids);
$orders = $stmt->fetchAll();

Результат: выполняется запрос с тремя параметрами - безопасно даже при нечисловых значениях (PDO приводит их к типу).

Пример 5. Защита ORDER BY через белый список

Безопасная сортировка:

Пример
$allowed = ['price', 'name', 'created_at'];
$sort = $_GET['sort'];
if (!in_array($sort, $allowed)) {
    $sort = 'created_at';
}
$stmt = $pdo->prepare("SELECT * FROM products ORDER BY $sort");
$stmt->execute();
$products = $stmt->fetchAll();

Если злоумышленник передаст sort=price DESC; SELECT * FROM admin, запрос не выполнится - имя столбца не пройдёт проверку белого списка.

Пример 6. Использование MySQLi с bind_param

Аналог PDO на MySQLi:

Пример
$stmt = $mysqli->prepare('SELECT * FROM users WHERE email = ?');
$stmt->bind_param('s', $_POST['email']);
$stmt->execute();
$result = $stmt->get_result();
$user = $result->fetch_assoc();

Пояснение: символ ? - позиционный плейсхолдер, bind_param указывает тип ('s' - строка). Данные не интерпретируются как SQL.

SQL-инъекции в PHP - comments

En
Sql инъекция php (php)