SQL-инъекции: как возникают и как защититься
SQL-инъекция возникает, когда пользовательский ввод смешивается с текстом запроса и меняет его смысл.
SQL-инъекция — уязвимость, при которой непроверенные данные пользователя становятся частью SQL-запроса и изменяют его логику.
Откуда берётся проблема
Корень зла — склейка строк. Когда запрос собирают конкатенацией с пользовательским вводом, граница между «командой» и «данными» стирается. Рассмотрим уязвимый псевдокод (это иллюстрация проблемы, не запускаемый блок):
// ОПАСНО: ввод подставлен прямо в текст запроса
login = ввод_пользователя
query = "SELECT * FROM users WHERE name = '" + login + "'"
Если в поле login пользователь введёт обычное имя — всё хорошо. Но если ввод содержит кавычку и продолжение SQL, он «вырвется» из строкового литерала и допишет свою логику в запрос. Так непредусмотренный ввод превращается в исполняемую команду к базе. Принцип угрозы важно понимать, чтобы её закрыть.
К чему это приводит
Через такую дыру можно прочитать чужие данные, обойти проверку пароля или повредить таблицы. Это одна из старейших и до сих пор частых уязвимостей именно потому, что склеивать строки — соблазнительно просто.
Решение: параметризованные запросы
Правильный подход — никогда не вставлять ввод в текст запроса. Вместо этого в запросе ставят placeholder (заполнитель), а данные передают отдельно. База обрабатывает их строго как значение, а не как часть команды. Это называют параметризованными запросами или prepared statements.
// БЕЗОПАСНО: ? — заполнитель, login передаётся как данные
query = "SELECT * FROM users WHERE name = ?"
выполнить(query, [login]) // login никогда не станет командой
Теперь, что бы пользователь ни ввёл — хоть кавычки, хоть ключевые слова SQL, — это останется просто строкой-значением. Граница между кодом и данными восстановлена. Параметризация закрывает SQL-инъекции полностью, а не «уменьшает риск».
Безопасный запрос работает как обычно
Параметризованный запрос — это обычный SQL, просто значения отделены. Вот рабочий пример самой выборки (запустите в SQL-песочнице):
CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, role TEXT);
INSERT INTO users (name, role) VALUES ('alice', 'admin');
INSERT INTO users (name, role) VALUES ('bob', 'user');
-- Здесь 'alice' играет роль безопасно переданного параметра
SELECT id, name, role FROM users WHERE name = 'alice';
Запрос вернёт строку Алисы. В реальном коде значение 'alice' вы бы не вписывали в текст, а передавали отдельным параметром — но сам SELECT выглядит точно так же.
Дополнительные слои защиты
- Валидация ввода. Ожидаете число — проверьте, что пришло число. Это не замена параметризации, а дополнение.
- Наименьшие привилегии. Учётная запись приложения к БД не должна иметь прав удалять таблицы, если ей это не нужно.
- ORM и query builder. Большинство современных библиотек параметризуют запросы автоматически — но только если не подсовывать им «сырые» строки.
Итог
- SQL-инъекция рождается из склейки запроса со строкой пользователя.
- Параметризованные запросы передают данные отдельно от текста команды и закрывают проблему полностью.
- Валидация и наименьшие привилегии — дополнительные слои, а не замена параметризации.
- ORM помогает, но только пока вы не вставляете сырые строки вручную.