arrow_backНазад к полевым заметкам
WEB SECURITY Опубликовано 8 Aug 2026

Что такое SQL-инъекция и как её предотвратить?

Практический разбор SQL-инъекций, реальные примеры и конкретные решения, которые реально работают в production-коде.

SQL-инъекция существует с конца 1990-х годов и остаётся одним из самых частых способов компрометации веб-приложений. Уязвимость проста: атакующий отправляет данные, которые интерпретируются как SQL-код вместо обычных данных, и база данных выполняет то, что никогда не должна была выполнять.

Как атака работает на практике

Представьте форму входа, которая строит запрос в PHP вроде этого:

$query = "SELECT * FROM users WHERE username = '$username' AND password = '$password'";

Если приложение не проверяет входные данные, атакующий вводит admin' -- в поле username. Запрос становится:

SELECT * FROM users WHERE username = 'admin' --' AND password = ''

-- комментирует остаток строки, так что проверка пароля никогда не происходит. Это классический обход аутентификации. Атакующие также используют UNION SELECT для извлечения данных из других таблиц или складывают запросы через точку с запятой для выполнения чего-то разрушающего типа DROP TABLE users;, если драйвер позволяет несколько выражений.

Есть также слепая SQL-инъекция, когда приложение не показывает результаты запроса напрямую. Атакующие получают информацию через timing (SLEEP(5) в MySQL) или логические ответы (ведёт ли себя страница по-разному для условия true и false). Инструменты вроде sqlmap автоматизируют такое извлечение, когда найдена уязвимая переменная.

Почему конкатенация строк — коренная проблема

Все варианты этой атаки сводятся к одному: смешиванию кода и данных в одной строке. База данных не может отличить легитимное значение от внедрённого синтаксиса, потому что они приходят по одному каналу. Экранирование кавычек помогает в некоторых случаях, но это хрупко — разные кодировки, инъекция второго порядка (данные сохранены один раз, потом повторно использованы небезопасно позже) и особенности драйвера создают пути обхода. Экранирование — это заплатка, не решение.

Параметризованные запросы — настоящее решение

Решение — разделить SQL-код и данные пользователя на уровне драйвера, используя параметризованные запросы (также называются подготовленные выражения). База данных получает структуру запроса первой, потом присоединяет значения после, так что пользовательский ввод никогда не может изменить смысл запроса.

В Python с psycopg2:

cur.execute("SELECT * FROM users WHERE username = %s AND password = %s", (username, password))

В Node.js с mysql2:

connection.execute('SELECT * FROM users WHERE username = ? AND password = ?', [username, password]);

В Java с JDBC:

PreparedStatement stmt = conn.prepareStatement("SELECT * FROM users WHERE username = ? AND password = ?");
stmt.setString(1, username);
stmt.setString(2, password);

Обратите внимание на паттерн: заполнители (%s, ?) зарезервируют место для данных, а реальные значения передаются отдельно. Никакой конкатенации строк, никакого ручного экранирования не требуется. Это работает для подавляющего большинства запросов, которые вы напишете в обычном приложении.

Что с динамическими именами таблиц или столбцов?

Параметризация обрабатывает значения, но не идентификаторы — вы не можете присоединить имя таблицы как параметр. Если приложение должно выбирать таблицу динамически (редко и часто пахнет плохим дизайном), внесите в белый список разрешённые значения против заранее определённого списка вместо прямого доверия пользовательскому вводу:

allowed_tables = {'orders', 'invoices', 'customers'}
if table_name not in allowed_tables:
    raise ValueError("Invalid table")

Никогда не строите имена идентификаторов через форматирование строк из пользовательского ввода, даже с экранированием.

Многоуровневая защита вне самого запроса

Параметризованные запросы — основной контроль, но несколько других вещей имеют значение:

  • Принцип наименьших привилегий на аккаунте базы данных. Пользователь БД приложения не должен иметь DROP, ALTER или доступ к несвязанным схемам. Если инъекция всё же проскочит, ограниченные привилегии ограничат ущерб.
  • ORM помогают по умолчанию. Django ORM, SQLAlchemy и Hibernate все параметризуют запросы автоматически, когда вы используете их стандартные методы построения запросов. Риск появляется снова, когда разработчики переходят на raw SQL или используют вызовы .extra()/text() с интерполяцией строк — проверяйте эти места специально.
  • Валидация входных данных — вторичный слой, не замена. Проверка того, что поле email выглядит как email, хорошая практика, но это не останавливает инъекцию само по себе — атакующие находят творческие payload'ы, которые всё ещё проходят слабую валидацию.
  • WAF может поймать известные паттерны атак, но это слой обнаружения, не исправление основного кода.

Тестирование собственного кода на эту уязвимость

Пропустите запросы через инструмент статического анализа (Bandit для Python, Semgrep с наборами правил SQL-инъекции) как часть CI. Для ручного тестирования попробуйте внедрить одиночную кавычку (') в каждое поле ввода и следите за SQL-ошибками в ответе — часто это первый признак, что запрос не параметризован.

Если хотите углубиться в это, трек Web Security в Korra Studio охватывает инъекции наряду с XSS и обходом аутентификации, а сегменты про Databases разбирают паттерны проектирования запросов, которые избегают этого класса уязвимостей полностью.

Написано с помощью ИИ, проверено и опубликовано Михалом Пильхом (CISSP), Korra Studio.

Готовы пойти дальше?

Это одна заметка из базы знаний Korra Studio — платформа сочетает каждую тему с наставничеством один на один.

Начать бесплатноarrow_forward