Руководство для начинающих по SQL-инъекциям (и как их предотвратить)
Вы создаёте функцию поиска для своего приложения. Пользователи вводят название продукта, ваш бэкенд выполняет запрос к базе данных, и появляются результаты. Достаточно просто — пока кто-то не введёт '; DROP TABLE products;-- в это поле поиска.
Это SQL-инъекция, и она остаётся одной из самых опасных уязвимостей в веб-разработке. OWASP Top 10 относит «Инъекции» к критическим рискам безопасности, и SQL-инъекция находится в центре этой категории. Если вы пишете код, который взаимодействует с базой данных — даже время от времени — вам необходимо понимать, как работает эта атака и как её остановить.
Ключевые выводы
- SQL-инъекция возникает, когда злоумышленники манипулируют запросами к базе данных, внедряя вредоносный код через пользовательский ввод
- Параметризованные запросы (prepared statements) — основная защита, разделяющая SQL-код и данные
- ORM не являются автоматически безопасными при использовании методов сырых запросов
- Используйте белые списки (allowlists) для динамических идентификаторов, таких как имена таблиц и столбцов, которые нельзя параметризовать
- Применяйте эшелонированную защиту: учётные записи с минимальными привилегиями, централизованные слои доступа к данным и тестирование безопасности в CI-конвейерах
Что такое SQL-инъекция?
SQL-инъекция возникает, когда злоумышленник манипулирует запросами вашего приложения к базе данных, внедряя вредоносный код через пользовательский ввод. Вместо того чтобы обрабатывать ввод как данные, база данных выполняет его как команды.
Рассмотрим форму входа. Ваш бэкенд может создавать запрос следующим образом:
SELECT * FROM users WHERE email = 'user@example.com' AND password = 'secret123'
Если вы строите этот запрос путём конкатенации строк с пользовательским вводом, злоумышленник может ввести ' OR '1'='1 в качестве пароля. Результирующий запрос станет таким:
SELECT * FROM users WHERE email = 'user@example.com' AND password = '' OR '1'='1'
Поскольку '1'='1' всегда истинно, запрос возвращает всех пользователей, полностью обходя аутентификацию.
Почему SQL-инъекция всё ещё актуальна
Вы можете предположить, что современные фреймворки решили эту проблему. Это не так — по крайней мере, не автоматически.
Уязвимости SQL-инъекции встречаются в:
- API-эндпоинтах, принимающих JSON с параметрами фильтрации
- Формах поиска с множественными опциями запросов
- Административных панелях с динамической сортировкой или фильтрацией
- Конструкторах отчётов с пользовательскими критериями
Любое место, где пользовательский ввод влияет на запрос к базе данных, является потенциальной поверхностью атаки. Последствия варьируются от кражи данных до полного уничтожения базы данных.
Как происходит небезопасное построение запросов
Первопричина всегда одна и та же: обработка пользовательского ввода как доверенного SQL-кода, а не как данных.
Конкатенация строк — основной виновник:
// Уязвимо - никогда так не делайте
const query = `SELECT * FROM products WHERE name = '${userInput}'`;
Шаблонные литералы и форматирование строк создают ту же проблему:
# Уязвимо - никогда так не делайте
query = f"SELECT * FROM products WHERE category = '{category}'"
Даже ORM не являются автоматически безопасными, когда вы используете методы сырых запросов:
// Уязвимо - сырые запросы обходят защиту ORM
db.query(`SELECT * FROM users WHERE id = ${req.params.id}`);
Предотвращение SQL-инъекций с помощью параметризованных запросов
Основная защита от SQL-инъекций — использование параметризованных запросов (также называемых prepared statements). Они разделяют SQL-код и данные, гарантируя, что пользовательский ввод никогда не выполняется как команды.
Вот безопасный подход в JavaScript:
// Безопасно - параметризованный запрос
const query = 'SELECT * FROM products WHERE name = ?';
db.query(query, [userInput]);
И в Python:
# Безопасно - параметризованный запрос
cursor.execute("SELECT * FROM products WHERE name = %s", (user_input,))
База данных обрабатывает параметр как буквальное значение, а не как SQL-код. Даже если кто-то введёт '; DROP TABLE products;--, база данных будет искать продукт с этой точной строкой в качестве имени.
Что не покрывают параметризованные запросы
Идентификаторы (имена таблиц, имена столбцов) не могут быть параметризованы. Если вам нужна динамическая сортировка:
// Используйте белый список для имён столбцов
const allowedColumns = ['name', 'price', 'created_at'];
const sortColumn = allowedColumns.includes(userInput) ? userInput : 'name';
const query = `SELECT * FROM products ORDER BY ${sortColumn}`;
Хранимые процедуры безопасны только в том случае, если они избегают динамического SQL внутри. Хранимая процедура, которая конкатенирует строки, так же уязвима.
Почему экранирования недостаточно
Ручное экранирование и санитизация строк недостаточны сами по себе. Они подвержены ошибкам, специфичны для конкретной базы данных и легко реализуются неправильно. Параметризованные запросы обрабатывают экранирование автоматически и корректно.
Discover how at OpenReplay.com.
Эшелонированная защита
Параметризованные запросы — ваша основная защита, но дополнительные уровни помогают:
- Учётные записи баз данных с минимальными привилегиями: пользователь базы данных вашего приложения не должен иметь административных разрешений, таких как DROP, ALTER или CREATE USER в продакшене
- Централизованный слой доступа к данным: направляйте все запросы через единый модуль, который обеспечивает параметризацию
- Ревью кода и тестирование: включите проверки на SQL-инъекции в процесс ревью и CI-конвейер
Современные рекомендации от OWASP ASVS и инициативы CISA «Secure by Design» подчёркивают встраивание безопасности в процесс разработки, а не её добавление постфактум.
Заключение
SQL-инъекция остаётся опасной, потому что её легко внести и разрушительна при эксплуатации. Решение простое: используйте параметризованные запросы для всех операций с базой данных, валидируйте идентификаторы с помощью белых списков и никогда не доверяйте пользовательскому вводу.
Сделайте параметризованные запросы своим стандартом. Ваше будущее «я» — и ваши пользователи — скажут вам спасибо.
Часто задаваемые вопросы
Да. NoSQL-инъекция — это связанная уязвимость, затрагивающая базы данных вроде MongoDB. Злоумышленники могут манипулировать операторами запросов через пользовательский ввод. Защита аналогична: используйте встроенные методы запросов драйвера базы данных вместо построения запросов из строк и валидируйте весь пользовательский ввод перед использованием в запросах.
ORM обеспечивают защиту, когда вы используете их стандартные методы запросов. Однако большинство ORM предлагают функции сырых запросов, которые обходят эту защиту. Если вы используете методы сырого SQL или интерполяцию строк в запросах ORM, вы всё ещё уязвимы. Всегда используйте параметризованные методы даже в коде ORM.
Используйте автоматизированные инструменты, такие как SQLMap или OWASP ZAP, для сканирования на уязвимости. Включите модульные тесты, ориентированные на безопасность, которые пытаются внедрить вредоносные данные. Проводите ревью кода, специально проверяя конкатенацию строк в запросах. Рассмотрите пентестинг для критически важных приложений перед развёртыванием в продакшене.
Нет. Валидация ввода помогает уменьшить поверхность атаки, но никогда не должна быть вашей единственной защитой. Умные злоумышленники часто могут обойти правила валидации. Параметризованные запросы — основная защита, потому что они фундаментально разделяют код и данные. Используйте валидацию как дополнительный уровень, а не как замену правильному построению запросов.
Gain control over your UX
See how users are using your site as if you were sitting next to them, learn and iterate faster with OpenReplay. — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data. Check our GitHub repo and join the thousands of developers in our community.