Вопрос с собеседования · Основы

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

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

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

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

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

Подробно

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

"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 само по себе исключает инъекции.

Проверьте себя

Один верный вариант. Подумайте, потом откройте ответ.

Показать варианты и ответ
  • ✓Параметризованные запросы: текст запроса и данные передаются отдельно; имена колонок — только из белого списка
  • ✗Экранировать кавычки во вводе пользователя вручную или через ORM, а имена колонок и направление сортировки передавать параметрами запроса
  • ✗ORM защищает даже при сборке сырого SQL из строк
  • ✗Направление сортировки можно передать параметром запроса

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

Партнёр платформы

Разбор собеседования с sotto

После собеседования sotto разбирает разговор: оценка, каждый вопрос — хорошо, частично или слабо, чего не хватило в ответе и над чем поработать к следующему разу. Первые 30 минут бесплатно.

Разбор собеседования