XSS, SQL-инъекции и command injection простыми словами
Эти три уязвимости веб-разработчик слышит чаще остальных: на собеседовании, в отчёте сканера, в комментарии на код-ревью. Звучат они по-разному, но устроены одинаково. Программа берёт то, что прислал пользователь, и вставляет это туда, где ожидается код: в SQL-запрос, в HTML-страницу или в команду операционной системы. Если не отделить данные от команд, «данные» начинают исполняться.
Ниже — объяснение без лишней теории, учебные примеры уязвимого и исправленного кода на Python и JavaScript и ссылки на рекомендации OWASP.
Важно про законность. Проверять уязвимости можно только в своих приложениях, на учебных стендах или по письменному разрешению владельца системы, например в рамках программы bug bounty и её правил. Неправомерный доступ к компьютерной информации и создание вредоносных программ — уголовные преступления (статьи 272 и 273 УК РФ). Примеры в статье нужны, чтобы находить и исправлять ошибки в своём коде.
Общий корень: данные превращаются в код
OWASP описывает инъекцию так: это изъян, при котором недоверенный ввод пользователя передаётся интерпретатору, и тот исполняет часть этого ввода как команды. Интерпретатором может быть СУБД, браузер, командная оболочка, LDAP-сервер или шаблонизатор.
В актуальном рейтинге OWASP Top 10:2025 инъекции занимают пятое место (A05:2025 Injection). В 2021 году они были третьими. Категория по-прежнему огромная: к ней отнесено 37 типов слабостей (CWE). Только по XSS (CWE-79) насчитывается больше 30 тысяч CVE, по SQL-инъекциям (CWE-89) — больше 14 тысяч. OWASP отмечает, что XSS встречается очень часто, но обычно бьёт слабее, а SQL-инъекция находится реже, зато последствия у неё серьёзнее.
SQL-инъекция
Что это
Приложение собирает SQL-запрос, просто склеивая строку с тем, что ввёл пользователь. Если во вводе есть символы, значимые для SQL (например, кавычка), они меняют структуру запроса. Тогда пользователь управляет уже не значением, а логикой запроса.
Уязвимый код
# Плохо: значение вклеивается прямо в текст запроса
def find_user(cursor, email):
cursor.execute(f"SELECT id, name FROM users WHERE email = '{email}'")
return cursor.fetchone()
Пока в email приходит обычный адрес, всё работает. Но если строка содержит
апостроф, она закрывает кавычку раньше времени, а остаток ввода становится частью
SQL. Классический учебный пример — ' OR '1'='1. Условие WHERE превращается в
«всегда истина», и запрос возвращает чужие записи. В реальных атаках последствия
бывают гораздо хуже: чтение всей базы, изменение данных, обход входа.
Исправленный код
# Хорошо: запрос и данные передаются в драйвер раздельно
def find_user(cursor, email):
cursor.execute("SELECT id, name FROM users WHERE email = %s", (email,))
return cursor.fetchone()
Здесь %s — не форматирование строки Python, а место для параметра. Драйвер СУБД
(в примере — psycopg для PostgreSQL) передаёт значение отдельно от текста запроса,
и оно не может изменить его структуру. В Django то же самое делает ORM:
User.objects.filter(email=email) безопасен. Если нужен сырой SQL, передавайте
параметры списком: User.objects.raw("... WHERE email = %s", [email]).
Подвох с именами таблиц и столбцов
Параметры подставляются только на место значений. Имя столбца для сортировки или имя таблицы так передать нельзя. OWASP прямо предупреждает: такие части запроса не экранируются, поэтому принимать их от пользователя опасно. Решение — список разрешённых значений:
SORT_COLUMNS = {"name": "name", "date": "created_at"}
column = SORT_COLUMNS.get(requested_sort, "created_at") # всё остальное игнорируем
Дополнительно стоит ограничить права учётной записи БД, под которой работает приложение. Если инъекция всё же случится, она не позволит удалить таблицы или прочитать то, что приложению не нужно.
XSS (межсайтовый скриптинг)
Что это
XSS (cross-site scripting) — это ситуация, когда сайт вставляет пользовательские данные в страницу так, что браузер воспринимает их как HTML или JavaScript. Скрипт выполняется в браузере жертвы от имени уязвимого сайта. Он может действовать от лица пользователя, читать данные на странице, подменять содержимое.
Обычно выделяют три вида:
- отражённый — вредоносный ввод приходит в запросе (например, в параметре ссылки) и сразу возвращается в ответе;
- хранимый — ввод сохраняется на сервере (комментарий, имя профиля) и показывается всем, кто откроет страницу;
- DOM-based — уязвим сам JavaScript на клиенте, который берёт данные из URL или другого источника и вставляет их в страницу.
Уязвимый и исправленный код
const name = new URLSearchParams(location.search).get("name");
// Плохо: строка разбирается как HTML, теги внутри неё станут элементами страницы
greeting.innerHTML = "Привет, " + name;
// Хорошо: строка вставляется как текст, теги отобразятся буквами
greeting.textContent = "Привет, " + name;
На сервере принцип тот же. В Django-шаблоне {{ comment.text }} автоматически
экранирует <, >, кавычки и амперсанд, поэтому разметка из комментария выведется
как текст. Защиту отключают фильтр |safe и функция mark_safe(). Каждое их
использование с пользовательскими данными стоит считать потенциальной уязвимостью.
Как защищаются
- Экранирование по контексту вывода. Для HTML-текста, атрибута, JavaScript и URL правила разные. Шаблонизаторы с автоэкранированием (Django, Jinja2 с включённым autoescape, React по умолчанию) берут большую часть работы на себя.
- Безопасные API в браузере:
textContentвместоinnerHTML,setAttributeдля безопасных атрибутов вместо сборки HTML строкой. - Санитизация, если HTML действительно нужен. Для форматированных комментариев используют проверенную библиотеку-санитайзер (например, DOMPurify), а не самописные регулярные выражения.
- Content Security Policy. Заголовок CSP ограничивает, откуда браузеру разрешено загружать и исполнять скрипты. Это второй рубеж обороны, а не замена экранированию.
- Флаг HttpOnly для cookie сессии. Скрипт на странице не сможет прочитать такую cookie.
Command injection (внедрение команд ОС)
Что это
Приложение вызывает системную команду и подставляет в неё пользовательский ввод.
Если команда идёт через оболочку (shell), символы вроде ;, | или && позволяют
дописать к ней ещё одну. Последствия обычно самые тяжёлые из трёх: выполнение
произвольных команд на сервере с правами приложения. На сленге это и есть RCE
(remote code execution).
Уязвимый код
import subprocess
# Плохо: строка целиком отдаётся оболочке
def ping(host):
result = subprocess.run(f"ping -c 1 {host}", shell=True,
capture_output=True, text=True)
return result.stdout
Исправленный код
import ipaddress
import subprocess
# Хорошо: ввод проверяется, аргументы передаются списком, без оболочки
def ping(host):
address = ipaddress.ip_address(host) # ValueError, если это не IP-адрес
result = subprocess.run(["ping", "-c", "1", str(address)],
capture_output=True, text=True, timeout=5)
return result.stdout
Здесь сработали три меры сразу. Во-первых, команда запускается без оболочки:
ping получает адрес как один аргумент, и спецсимволы ничего не значат. Документация
Python отдельно предупреждает об опасности shell=True. Во-вторых, ввод проверяется
по строгому формату, то есть по белому списку, а не по списку «запрещённых символов».
В-третьих, есть таймаут. Ещё лучше вообще не вызывать внешние программы, если ту же
задачу решает библиотека: обработку изображений, архивы, DNS-запросы.
Как защищаются от инъекций в целом
У всех трёх уязвимостей одно лекарство: держать данные отдельно от команд. На практике это выглядит так.
- Безопасные API вместо склейки строк: параметризованные запросы и ORM,
автоэкранирующие шаблоны,
subprocessсо списком аргументов. - Проверка ввода по белому списку: ожидаемый тип, длина, формат. Это не заменяет пункт 1, но сильно сужает возможности атакующего.
- Минимальные привилегии: у учётной записи БД и у процесса приложения только те права, которые действительно нужны.
- Многоуровневая защита: CSP, HttpOnly-cookie, WAF. Это страховка на случай ошибки в коде, но не основная мера.
- Проверки в процессе разработки: код-ревью с вниманием к местам, где ввод попадает в интерпретатор, статический анализ (SAST), тесты на граничные значения, обновление зависимостей.
OWASP: что почитать дальше
OWASP (Open Worldwide Application Security Project) — некоммерческое сообщество, чьи материалы бесплатны и признаны индустрией. Для этой темы стоит держать под рукой:
- OWASP Top 10:2025, раздел A05 Injection — описание, статистика, список CWE;
- OWASP Cheat Sheet Series — короткие практические шпаргалки: SQL Injection Prevention, Cross Site Scripting Prevention и OS Command Injection Defense;
- OWASP Web Security Testing Guide — методология проверки веб-приложений, если хочется смотреть на код глазами тестировщика.
Где потренироваться законно
- PortSwigger Web Security Academy — бесплатные интерактивные лаборатории по SQL-инъекциям, XSS, внедрению команд и десяткам других тем. Атаковать можно только выданные вам учебные экземпляры.
- OWASP Juice Shop — намеренно уязвимый интернет-магазин, который запускается локально.
- DVWA (Damn Vulnerable Web Application) — классический учебный стенд, тоже для локального запуска.
Запускайте учебные стенды у себя или в изолированной среде и не выставляйте их в интернет: они уязвимы специально.
Похек
В Похеке мы разбираем уязвимости, рассказываем о профессии и шутим о вещах, понятных
тем, кто хоть раз ловил ' в поле логина.
- Telegram: t.me/poxek и t.me/poxek_ai
- YouTube: @poxek_official
- MAX: max.ru/poxek
- Мерч: футболка XSS & SQLi — XSS на груди, SQL-инъекция на спине. Остальные вещи — в каталоге.
Источники
- OWASP Top 10:2025 — список категорий: https://top10.owasp.org/2025/
- OWASP Top 10:2025, A05 Injection: https://top10.owasp.org/2025/A05_2025-Injection/
- OWASP Cheat Sheet Series. SQL Injection Prevention: https://cheatsheetseries.owasp.org/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.html
- OWASP Cheat Sheet Series. Cross Site Scripting Prevention: https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html
- OWASP Cheat Sheet Series. OS Command Injection Defense: https://cheatsheetseries.owasp.org/cheatsheets/OS_Command_Injection_Defense_Cheat_Sheet.html
- OWASP Web Security Testing Guide: https://owasp.org/www-project-web-security-testing-guide/
- Django documentation. Security in Django: https://docs.djangoproject.com/en/stable/topics/security/
- Python documentation. subprocess — Security Considerations: https://docs.python.org/3/library/subprocess.html#security-considerations
- PortSwigger Web Security Academy: https://portswigger.net/web-security
- OWASP Juice Shop: https://owasp.org/www-project-juice-shop/
- DVWA: https://github.com/digininja/DVWA
- УК РФ, ст. 272 (КонсультантПлюс): https://www.consultant.ru/document/cons_doc_LAW_10699/5c337673c261a026c476d578035ce68a0ae86da0/
- УК РФ, ст. 273 (КонсультантПлюс): https://www.consultant.ru/document/cons_doc_LAW_10699/a4d58c1af8677d94b4fc8987c71b131f10476a76/