SQL-инъекции в PHP: как защититься в 2025 году

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Вступление: почему SQL-инъекции всё ещё актуальны?

Представьте: ваш сайт работает как часы, пользователи регистрируются, оставляют комментарии, оформляют заказы. И вдруг — база данных оказывается в руках злоумышленника. Не потому, что хакеры гениальны, а потому, что кто-то забыл поставить знак вопроса в SQL-запросе.

SQL-инъекции — это не архаичная угроза из 2000-х. В 2024 году OWASP всё ещё ставит их в топ-3 самых опасных уязвимостей веб-приложений. А вопрос "How can I prevent SQL injection in PHP?" на Stack Overflow набрал более 1.5 миллиона просмотров — и продолжает расти. Значит, проблема жива.

В этой статье мы разберёмся, как намертво закрыть дверь для SQL-инъекций в PHP в 2025 году. Без магии, без сложных фреймворков, только чистый код и лучшие практики.

Что такое SQL-инъекция простыми словами?

SQL-инъекция — это когда пользовательский ввод становится частью SQL-команды, а не просто данными. Пример из жизни:

<?php
// ПЛОХОЙ КОД — никогда так не делайте!
$username = $_POST['username'];
$query = "SELECT * FROM users WHERE username = '$username'";
$result = mysqli_query($conn, $query);
?>

Если злоумышленник введёт в поле username строку ' OR '1'='1, запрос превратится в:

SELECT * FROM users WHERE username = '' OR '1'='1'

Это вернёт всех пользователей. А если ввести '; DROP TABLE users; -- — можно потерять всю таблицу. Страшно? Ещё бы.

Лучшие практики защиты от SQL-инъекций в PHP 2025

1. Используйте PDO с подготовленными запросами (это стандарт)

PDO (PHP Data Objects) — это универсальный интерфейс для работы с базами данных. Его главное преимущество: он разделяет SQL-логику и данные. Вы пишете запрос с плейсхолдерами, а PDO сам заботится о безопасном экранировании.

<?php
// ХОРОШИЙ КОД — используйте PDO
$dsn = 'mysql:host=localhost;dbname=testdb;charset=utf8mb4';
$pdo = new PDO($dsn, $user, $pass, [
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
    PDO::ATTR_EMULATE_PREPARES => false, // отключаем эмуляцию
]);

$stmt = $pdo->prepare('SELECT * FROM users WHERE email = :email');
$stmt->execute(['email' => $_POST['email']]);
$user = $stmt->fetch();
?>

Почему это работает? База данных получает структуру запроса отдельно от данных. Даже если пользователь введёт вредоносный SQL, он будет интерпретирован как обычная строка, а не как часть команды.

2. Mysqli с подготовленными запросами (альтернатива)

Если вы работаете только с MySQL, можно использовать mysqli — но только с подготовленными запросами, а не с экранированием через mysqli_real_escape_string().

<?php
$mysqli = new mysqli('localhost', 'user', 'pass', 'testdb');
$stmt = $mysqli->prepare('INSERT INTO comments (user_id, text) VALUES (?, ?)');
$stmt->bind_param('is', $userId, $commentText);
$stmt->execute();
?>

Важно: в 2025 году mysqli_real_escape_string() всё ещё существует, но полагаться на него для защиты — плохая идея. Он экранирует только строки, но не защищает от инъекций в числовых полях или в операторах LIKE.

3. Фильтрация и валидация — второй рубеж обороны

Подготовленные запросы — это основа. Но добавьте к ним фильтрацию по типу данных. Если ожидается число — проверьте, что это число. Если email — проверьте формат.

<?php
$id = filter_input(INPUT_POST, 'user_id', FILTER_VALIDATE_INT);
if ($id === false) {
    throw new InvalidArgumentException('ID must be an integer');
}

$stmt = $pdo->prepare('SELECT * FROM users WHERE id = :id');
$stmt->execute(['id' => $id]);
?>

Комбинируйте filter_var(), ctype_digit() для чисел и регулярные выражения для сложных форматов. Это не только безопасность, но и корректность данных.

4. Избегайте динамических запросов любой ценой

Иногда так хочется собрать запрос из кусочков: добавить ORDER BY, изменить LIMIT, подставить имя таблицы. Это самый опасный путь. Если без динамики не обойтись, используйте белый список разрешённых значений:

<?php
$allowedColumns = ['id', 'name', 'email', 'created_at'];
$sortBy = $_GET['sort'] ?? 'id';

if (!in_array($sortBy, $allowedColumns, true)) {
    $sortBy = 'id';
}

$stmt = $pdo->prepare("SELECT * FROM users ORDER BY $sortBy");
$stmt->execute();
?>

Никогда не подставляйте имена таблиц или столбцов напрямую из пользовательского ввода!

5. Настройте PDO правильно

Даже с подготовленными запросами можно оставить лазейку, если неправильно настроить PDO. Вот критически важные настройки:

  • Отключите эмуляцию подготовленных запросов: PDO::ATTR_EMULATE_PREPARES => false. Иначе PDO может подставлять данные в запрос на стороне PHP, а не на стороне БД, и вы теряете защиту.
  • Включите режим исключений: PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION. Это поможет отлавливать ошибки раньше, чем они станут уязвимостью.
  • Установите кодировку в DSN: charset=utf8mb4. Это защищает от некоторых атак через многобайтовые символы.

6. Используйте ORM для сложных проектов

Если вы строите крупное приложение, рассмотрите ORM (Object-Relational Mapping): Doctrine, Eloquent (Laravel), Propel. Они практически полностью исключают ручное написание SQL-запросов.

<?php
// Пример с Eloquent (Laravel)
$user = User::where('email', $request->input('email'))->first();
// Под капотом — подготовленные запросы
?>

ORM — это не

Блоги

Рекомендаций по книгам