Инъекция возникает, когда данные пользователя попадают в интерпретатор (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"
Шелл разбирает ;, |, &&, $(), обратные кавычки. Защита:
- Не вызывать шелл: передавать аргументы списком (
subprocess.run(["ping", "-c", "1", host]), в NodeexecFile, а неexec). - Валидировать по allowlist: хост это IP или имя по строгому регулярному выражению.
- Остерегаться инъекции опций: значение
-oProxyCommand=...воспримется как флаг. Помогает разделитель--и запрет ведущего-. - По возможности использовать библиотеку вместо внешней программы.
Другие инъекции
- 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 от инъекций.