SQL-инъекции: почему возникают и как закрыть

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

Что это такое

SQL-инъекция возникает, когда данные от пользователя попадают прямо в текст SQL-запроса. База данных не отличает «команду программиста» от «данных пользователя», если их склеить в одну строку. В результате ввод пользователя может изменить смысл запроса.

Корень проблемы — склейка строк

Представим, что разработчик строит запрос так (это небезопасный пример, показывающий принцип):

query = "SELECT * FROM users WHERE name = '" + user_input + "'"

Если пользователь введёт обычное имя, всё работает. Но если ввод содержит кавычку и служебные символы SQL, структура запроса ломается, и условие может стать всегда истинным. Проблема не в SQL и не в пользователе, а в том, что данные смешали с кодом.

Защита: параметризованные запросы

Правильное решение — никогда не склеивать пользовательский ввод с текстом запроса. Вместо этого используют параметры (placeholders). База получает шаблон запроса и отдельно — значения. Значение всегда трактуется как данные, а не как команда.

import sqlite3

con = sqlite3.connect(":memory:")
cur = con.cursor()
cur.execute("CREATE TABLE users(name TEXT, role TEXT)")
cur.execute("INSERT INTO users VALUES('alice','admin')")
cur.execute("INSERT INTO users VALUES('bob','user')")

# Безопасно: ? — это placeholder, значение передаётся отдельно
user_input = "alice"
cur.execute("SELECT role FROM users WHERE name = ?", (user_input,))
print("Роль:", cur.fetchone()[0])

# Даже «опасный» ввод теперь просто не найдёт совпадения
bad = "alice' OR '1'='1"
cur.execute("SELECT role FROM users WHERE name = ?", (bad,))
print("Опасный ввод нашёл записей:", len(cur.fetchall()))

Запустите код: «опасный» ввод во втором случае не сломает логику — он воспринимается как обычная строка-имя, которой просто нет в таблице.

Слои защиты

  • Параметризация — главный приём, закрывает большинство случаев.
  • ORM (например, Django ORM, SQLAlchemy) — по умолчанию используют параметры.
  • Принцип наименьших привилегий — у пользователя БД ровно те права, что нужны приложению.
  • Валидация ввода — дополнительный, но не основной слой.

Запомните формулу: данные и код нельзя смешивать. Это правило работает не только для SQL, но и для других инъекций.

Проверьте себя
1. Какой приём считается основной защитой от SQL-инъекций?
AЗапретить пользователям вводить кавычки
BПараметризованные запросы (placeholders), где данные передаются отдельно от текста запроса
CШифровать базу данных
DПрятать сообщения об ошибках
Поддержать проект