MySQLSeniorTechnical
Как предотвратить SQL injection при работе с MySQL?
SQL-инъекции предотвращаются параметризованными запросами (prepared statements) — никогда не интерполируйте пользовательский ввод в SQL. Дополнительно: принцип минимальных привилегий и WAF.
Предотвращение SQL-инъекций в MySQL
SQL-инъекция — атака, при которой вредоносный ввод изменяет структуру SQL-запроса. Единственная надёжная защита — параметризованные запросы, при которых данные и код SQL разделены на уровне протокола.
Уязвимый код (никогда не делайте так)
// УЯЗВИМО: прямая конкатенация
$query = "SELECT * FROM users WHERE username = '" . $_GET['user'] . "'";
// Ввод: admin' OR '1'='1
// Итоговый запрос: SELECT * FROM users WHERE username = 'admin' OR '1'='1'
Защита 1: Prepared Statements (PDO)
$pdo = new PDO('mysql:host=127.0.0.1;dbname=app', $user, $pass, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_EMULATE_PREPARES => false, // критично! реальные prepared statements
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]);
$stmt = $pdo->prepare(
'SELECT id, email FROM users WHERE username = :username AND active = :active'
);
$stmt->execute([
':username' => $username, // данные никогда не попадают в SQL-текст
':active' => 1,
]);
$user = $stmt->fetch();
Защита 2: MySQLi с bind_param
$mysqli = new mysqli('127.0.0.1', $user, $pass, 'app');
$mysqli->set_charset('utf8mb4'); // обязательно!
$stmt = $mysqli->prepare('SELECT id FROM users WHERE email = ? AND role = ?');
$stmt->bind_param('ss', $email, $role); // 's' = string, 'i' = int, 'd' = double
$stmt->execute();
$result = $stmt->get_result();
Защита 3: ORM (Eloquent / Doctrine)
// Eloquent автоматически параметризует
$users = User::where('email', $email)
->where('role', $role)
->get();
// Raw-запросы в Eloquent тоже безопасны через биндинг
$users = DB::select(
'SELECT * FROM users WHERE email = ? AND created_at > ?',
[$email, $date]
);
// ОПАСНО: DB::statement с интерполяцией:
// DB::statement("DELETE FROM users WHERE id = $id"); // никогда!
Принцип минимальных привилегий
-- Создаём отдельного пользователя для приложения
CREATE USER 'app_user'@'%' IDENTIFIED BY 'strong_password';
-- Только необходимые права — никакого SUPER, DROP, FILE
GRANT SELECT, INSERT, UPDATE, DELETE ON appdb.* TO 'app_user'@'%';
-- Для readonly-реплики:
GRANT SELECT ON appdb.* TO 'app_readonly'@'%';
FLUSH PRIVILEGES;
Дополнительные меры
- Валидация и whitelist: если нужно динамически подставлять имя таблицы или колонки — валидируйте через whitelist, биндинг не поддерживает идентификаторы.
- Хранимые процедуры: инкапсулируют SQL и принимают параметры — не панацея, но уменьшают поверхность атаки.
- WAF: ModSecurity с OWASP CRS или AWS WAF с правилами для SQL-инъекций как дополнительный слой.
- Аудит: MySQL Audit Log Plugin или Percona Audit Log — фиксируют все запросы к БД для forensics.
Подводные камни
PDO::ATTR_EMULATE_PREPARES = true(значение по умолчанию!) делает биндинг иллюзорным — PDO сам интерполирует данные в строку; отключайте явно.- Биндинг не защищает от инъекций в идентификаторы (
ORDER BY $column) — используйте whitelist разрешённых значений. - htmlspecialchars() и addslashes() не защищают от SQL-инъекций — это инструменты для XSS и не заменяют параметризацию.
- Second-order injection: данные сохраняются «безопасно», но потом используются в другом запросе без параметризации — часто упускают при code review.
- Кодировка: без
set_charset('utf8mb4')в MySQLi многобайтные символы могут обойти экранирование в старых версиях. - ORM-методы типа
whereRaw(),selectRaw(),orderByRaw()принимают сырой SQL — вставка переменных без биндинга так же уязвима. - Сообщения об ошибках БД в ответе приложения раскрывают структуру таблиц — всегда логируйте ошибки сервер-сайд, пользователю возвращайте generic message.
- Недостаточно параметризовать только WHERE — LIKE-паттерны с
%и_внутри биндинга остаются мета-символами; экранируйте их дополнительно.
Common mistakes
- Не уточнять storage engine и говорить о предотвращение sql injection так, будто MyISAM и InnoDB ведут себя одинаково.
- Игнорировать implicit commit, autocommit, isolation level или binlog там, где они меняют ответ.
- Описывать PostgreSQL-поведение как MySQL-поведение.
- Забывать проверить план через EXPLAIN и фактическую версию MySQL.
What the interviewer is testing
- Кандидат объясняет предотвращение sql injection через реальный механизм MySQL, а не общими словами.
- Приводит корректный SQL или диагностический запрос для этой СУБД.
- Называет ограничения, версионные отличия или эксплуатационные последствия.
- Связывает ответ с проектированием приложения, производительностью, надежностью или безопасностью.