Получить доступ

Что такое SQL-инъекция и как от неё защититься?

JuniorБезопасность
Ещё спрашивают так:
  • Почему нельзя собирать SQL конкатенацией строк?
  • Как работают параметризованные запросы?
  • Защищает ли ORM от SQL-инъекций?
Коротко

Инъекция — данные пользователя попадают в текст запроса и меняют его смысл: ' OR '1'='1 превращает проверку пароля в «всегда истина».

  • Защита — параметризованные запросы: текст запроса и данные передаются в базу отдельно
  • ORM защищает, пока не собирать сырой SQL из строк
  • Имена колонок и направление сортировки параметром не передать — только белый список

А ещё Плюс: минимальные права пользователя базы, не показывать ошибки СУБД наружу

Так подсказку видит пользователь sotto на живом собеседовании — за пару секунд, пока вы думаете вслух.

Подробно

Если запрос собирается конкатенацией:

"SELECT * FROM users WHERE login = '" + login + "' AND password = '" + pass + "'"

то ввод admin' -- превращает остаток в комментарий, а x' OR '1'='1 делает условие истинным. Дальше хуже: UNION SELECT вытаскивает данные из других таблиц, в некоторых СУБД можно выполнить несколько команд (; DROP TABLE), прочитать файлы или выполнить команды ОС. Даже без видимого вывода работает «слепая» инъекция — по времени ответа (pg_sleep) или по разнице в поведении.

Главная защита — параметризованные запросы (prepared statements):

SELECT * FROM users WHERE login = ? AND password_hash = ?

Текст запроса и значения уходят в базу раздельно; значение никогда не разбирается как SQL, сколько бы кавычек в нём ни было. Поддержано везде: PreparedStatement в JDBC, плейсхолдеры в psycopg и SQLAlchemy, именованные параметры в JPA.

ORM использует параметры, но уязвимости возвращаются через сырой SQL: createNativeQuery("... " + filter), text(f"... {x}"), конкатенация в JPQL, extra() и raw() в Django.

Что нельзя передать параметром: имена таблиц и колонок, направление сортировки, ключевые слова. Для ORDER BY по выбору пользователя — белый список: сопоставить ввод с допустимыми колонками и подставить только известное значение. Для IN (...) — сгенерировать нужное число плейсхолдеров.

Дополнительные слои:

  • пользователь базы с минимальными правами (приложению не нужен DROP и доступ к чужим схемам);
  • не показывать текст ошибок СУБД пользователю;
  • валидация формата (id — число) — полезна, но не замена параметрам;
  • WAF как страховка, не как основная защита.

Тот же класс ошибок — любые инъекции в интерпретаторы: команды ОС (subprocess с shell=True), LDAP, NoSQL (операторы $ne в MongoDB из JSON), шаблоны, а теперь и промпты языковых моделей.

Пример

# уязвимо
cur.execute(f"SELECT * FROM orders WHERE id = {order_id}")
# безопасно: значение передаётся отдельно
cur.execute("SELECT * FROM orders WHERE id = %s", (order_id,))

# сортировка — белый список
SORT = {"date": "created_at", "sum": "total"}
cur.execute(f"SELECT * FROM orders ORDER BY {SORT.get(sort, 'created_at')} DESC")

Что хотят услышать

  • Механика: данные меняют структуру запроса, пример с кавычкой.
  • Параметризованные запросы как основная защита и почему они работают.
  • ORM не панацея: сырой SQL, нативные запросы.
  • Белый список для идентификаторов, минимальные права.

Частые ошибки

  • Экранировать кавычки вручную вместо параметров.
  • Думать, что использование ORM само по себе исключает инъекции.

Связанные вопросы

Закрытая бета

Подсказки на собеседовании — в реальном времени

sotto слышит вопрос интервьюера и показывает короткий ответ и план, как в блоке «Коротко». Оставьте контакт — пришлём доступ и промокод.

  • Промокод на 2 часа — сверх пробных 30 минут
  • Программа для Windows — скачать сразу, войти — через Яндекс ID или VK ID
  • Прямая связь с разработчиком: что улучшить — сделаем