Урок по треку Безопасность приложений 8 мин

Инъекции: SQL, NoSQL и команды ОС

Почему смешивание данных и кода опасно, параметризованные запросы, ORM, NoSQL-операторы и вызов процессов без шелла.

Инъекция возникает, когда данные пользователя попадают в интерпретатор (SQL, шелл, LDAP, шаблонизатор) как часть кода. Корень у всех инъекций один: смешивание данных и кода в одной строке. Защита тоже одна по сути: передавать данные отдельно от кода.

SQL-инъекция

Уязвимый код:

cur.execute(f"SELECT * FROM users WHERE email = '{email}'")

Если email равен ' OR '1'='1, условие становится всегда истинным. Через инъекцию можно читать чужие данные (UNION, слепые техники по времени и ошибкам), менять данные, а иногда выполнять команды на сервере БД.

Защита: параметризованные запросы. Текст запроса и значения уходят в драйвер раздельно, база никогда не разбирает значение как SQL:

cur.execute("SELECT * FROM users WHERE email = %s", (email,))
PreparedStatement ps = conn.prepareStatement("SELECT * FROM users WHERE email = ?");
ps.setString(1, email);

Тонкости:

  • Идентификаторы нельзя параметризовать: имя колонки для сортировки, имя таблицы. Их берут только из allowlist: {"name", "created_at"}, и всё остальное отклоняют.
  • ORM не спасает автоматически: raw(), whereRaw, text() с конкатенацией так же уязвимы.
  • Экранирование вручную хрупкое (кодировки, разные СУБД), это запасной вариант, а не основной.
  • Минимальные права учётки приложения: без DDL, без доступа к чужим схемам. Ограничивает ущерб.
  • Хранимые процедуры безопасны, только если внутри них нет динамического SQL из параметров.

NoSQL-инъекция

В MongoDB запросы строятся из объектов. Если тело запроса JSON прямо уходит в фильтр:

db.users.findOne({ email: req.body.email, password: req.body.password });

то тело {"email": "admin@x.ru", "password": {"$ne": null}} превращает проверку пароля в «пароль не null». Защита: проверять типы (typeof password === 'string'), валидировать схему входа (zod, Joi, Pydantic), удалять или запрещать ключи, начинающиеся с $, не использовать $where и серверный JavaScript.

Инъекция команд ОС

os.system(f"ping -c 1 {host}")      # host = "8.8.8.8; cat /etc/passwd"

Шелл разбирает ;, |, &&, $(), обратные кавычки. Защита:

  1. Не вызывать шелл: передавать аргументы списком (subprocess.run(["ping", "-c", "1", host]), в Node execFile, а не exec).
  2. Валидировать по allowlist: хост это IP или имя по строгому регулярному выражению.
  3. Остерегаться инъекции опций: значение -oProxyCommand=... воспримется как флаг. Помогает разделитель -- и запрет ведущего -.
  4. По возможности использовать библиотеку вместо внешней программы.

Другие инъекции

  • LDAP, XPath: те же принципы, параметризация или экранирование по спецификации.
  • SSTI (server-side template injection): пользовательский ввод становится шаблоном Jinja2 или Twig и выполняется. Нельзя рендерить шаблон из ввода, данные только как переменные.
  • Заголовки (CRLF): \r\n в значении заголовка позволяет добавить свои заголовки. Фреймворки обычно запрещают, но ручная сборка опасна.
  • Небезопасная десериализация (pickle.loads, Java-сериализация) из недоверенного источника может выполнить код.

Как искать

  • Ревью: любое склеивание строк с вводом рядом с execute, query, exec, system, eval.
  • SAST находит такие места по потокам данных.
  • DAST и ручное тестирование: кавычка в параметре, разница ответов, задержки.

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

  • «Мы экранируем кавычки, значит, защищены».
  • Параметризовать значения, но склеивать ORDER BY из запроса.
  • Валидировать только на фронтенде.
  • shell=True «для удобства».

Что спросят на собесе

  • Как работает SQL-инъекция и почему параметризация её предотвращает.
  • Как безопасно сделать сортировку по колонке из запроса.
  • Что такое NoSQL-инъекция в MongoDB.
  • Как безопасно вызвать внешнюю команду с аргументом от пользователя.
  • Защищает ли ORM от инъекций.

Закрепите за 5 минут

После урока в тренажёре 3-5 проверочных вопросов, дальше модуль уходит в повторения.