SQL-инъекции остаются одним из самых опасных и распространённых видов атак на веб-приложения. Через уязвимости в запросах к базе данных злоумышленники могут украсть конфиденциальную информацию, изменить или уничтожить данные, а в некоторых случаях получить полный контроль над сервером. В этом руководстве мы глубоко погрузимся в механизмы защиты с использованием PDO (PHP Data Objects) — современного и безопасного подхода к работе с базами данных в PHP.
Что такое SQL-инъекция и почему она опасна?
SQL-инъекция — это техника внедрения вредоносного SQL-кода в запросы приложения. Атака становится возможной, когда приложение некорректно обрабатывает пользовательский ввод, смешивая его с SQL-запросами без должной проверки и экранирования.
По данным OWASP Top 10, инъекционные атаки, включая SQL-инъекции, стабильно входят в тройку самых критичных уязвимостей веб-приложений последние десять лет.
Классический пример уязвимого кода
Рассмотрим типичную уязвимость в старом стиле с использованием mysql_* функций:
$username = $_POST['username'];
$password = $_POST['password'];
$query = "SELECT * FROM users WHERE username='$username' AND password='$password'";
$result = mysql_query($query);
Если злоумышленник введёт в поле username значение ' OR '1'='1, запрос превратится в:
SELECT * FROM users WHERE username='' OR '1'='1' AND password='...'
Условие '1'='1' всегда истинно, что может позволить обойти аутентификацию.
PDO: Современный подход к безопасности
PDO (PHP Data Objects) — это уровень абстракции доступа к данным, предоставляющий единый интерфейс для работы с различными СУБД. Его главное преимущество для безопасности — поддержка подготовленных выражений (prepared statements).
Как работают подготовленные выражения
Подготовленные выражения разделяют логику запроса и данные:
- Создаётся шаблон запроса с плейсхолдерами
- Шаблон компилируется и оптимизируется СУБД
- Данные передаются отдельно и автоматически экранируются
- СУБД выполняет запрос, гарантируя разделение кода и данных
Практическое применение PDO для защиты
1. Базовое подключение к базе данных
try {
$pdo = new PDO('mysql:host=localhost;dbname=database;charset=utf8mb4', 'username', 'password');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
} catch (PDOException $e) {
die('Ошибка подключения: ' . $e->getMessage());
}
Установка PDO::ATTR_EMULATE_PREPARES в false отключает эмуляцию подготовленных выражений на стороне PHP, заставляя использовать нативные механизмы СУБД, что повышает безопасность.
2. Использование именованных плейсхолдеров
$stmt = $pdo->prepare("SELECT * FROM users WHERE email = :email AND status = :status");
$stmt->execute([
':email' => $userEmail,
':status' => 'active'
]);
$user = $stmt->fetch();
3. Работа с IN-оператором
Особый случай — динамическое количество параметров в операторе IN:
$ids = [1, 2, 3, 5, 8];
$placeholders = str_repeat('?,', count($ids) - 1) . '?';
$stmt = $pdo->prepare("SELECT * FROM products WHERE id IN ($placeholders)");
$stmt->execute($ids);
Дополнительные меры безопасности
Валидация и санитизация входных данных
- Проверяйте тип данных (filter_var с FILTER_VALIDATE_*)
- Ограничивайте длину вводимых значений
- Используйте белые списки разрешённых символов
- Для сложных данных применяйте регулярные выражения
Принцип минимальных привилегий
Создавайте для приложения пользователя базы данных с минимально необходимыми правами:
- Ограничивайте доступ только к нужным таблицам
- Запрещайте DROP, TRUNCATE, GRANT операции
- Используйте разные учётные записи для чтения и записи
Логирование и мониторинг
Настройте логирование всех SQL-запросов и подозрительной активности:
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
Распространённые ошибки и как их избежать
Ошибка 1: Динамические имена таблиц и столбцов
Плейсхолдеры работают только для значений, не для идентификаторов. Для динамических имён таблиц используйте белые списки:
$allowedTables = ['users', 'products', 'orders'];
if (!in_array($tableName, $allowedTables)) {
throw new Exception('Недопустимое имя таблицы');
}
$stmt = $pdo->prepare("SELECT * FROM `$tableName` WHERE id = ?");
Ошибка 2: Неправильная обработка LIKE
Для оператора LIKE экранируйте специальные символы:
$search = '%' . str_replace(['%', '_'], ['\\%', '\\_'], $input) . '%';
$stmt = $pdo->prepare("SELECT * FROM articles WHERE title LIKE ?");
$stmt->execute([$search]);
Даже с использованием PDO остаётся риск второстепенных SQL-инъекций через уязвимости в самом PDO или драйверах СУБД. Регулярно обновляйте PHP и расширения.
FAQ: Часто задаваемые вопросы
В чём разница между PDO и mysqli?
PDO поддерживает 12 различных СУБД и предлагает более последовательный API. mysqli работает только с MySQL/MariaDB, но предоставляет некоторые специфичные для MySQL функции.
Можно ли полностью доверять подготовленным выражениям?
Да, при правильной настройке (отключённой эмуляции) подготовленные выражения PDO обеспечивают полную защиту от SQL-инъекций на уровне СУБД.
Нужно ли экранировать данные при использовании PDO?
Нет, если вы используете подготовленные выражения с плейсхолдерами. PDO автоматически экранирует и обрабатывает все передаваемые значения.
Как защитить старый код без полного переписывания?
Используйте функции типа pdo_quote() для экранирования значений, но рассматривайте это как временное решение. Полная миграция на подготовленные выражения — единственный надёжный путь.
Какие ещё уязвимости остаются после защиты от SQL-инъекций?
XSS (межсайтовый скриптинг), CSRF (межсайтовая подделка запроса), инъекции на уровне ОС, логические ошибки приложения. Безопасность требует комплексного подхода.