2026 г.

Веб-безопасность для разработчика

Проект ЦИТадель

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

Внешне SQL-инъекция, XSS, CSRF и SSRF устроены по-разному, но многие классические уязвимости объясняются двумя повторяющимися ошибками. Первая — смешение данных и команд, когда недоверенное значение попадает в контекст, где его исполняют. Вторая — неверная граница доверия, когда сервер принимает запрос, источник или сетевое назначение без необходимой проверки. Такое объяснение позволяет не только узнать атаку по имени, но и вывести защиту из её механизма.

Сначала статья разбирает инъекции на сервере и в браузере, контекстно безопасный вывод и CSP. Затем — межсайтовые запросы, атрибуты cookie и другие границы веб-приложения, включая SSRF, загрузку файлов и внешние зависимости. В завершение предлагается практикум, в котором каждая уязвимость сначала воспроизводится на изолированном стенде, а затем устраняется и закрепляется проверкой.

1. Граница между данными и командами

Байты становятся данными или командами в зависимости от того, как их использует интерпретатор. Инъекция возникает, когда недоверенное значение попадает в командный контекст: в текст SQL-запроса, команду оболочки, шаблон или исполняемую разметку. Основная защита — сохранять структурную границу: передавать значения через параметры и безопасные API, а вывод кодировать для конкретного контекста. Правила для HTML-текста, атрибута, URL, JavaScript, SQL и оболочки различаются; универсального «экранирования опасных символов» нет.

Не всякое использование строки является исполнением кода. Например, обход каталогов через ../ заставляет файловый API открыть не тот ресурс, но не превращает путь в программу. Здесь нужны нормализация, привязка результата к разрешённому каталогу и правила доступа. Поэтому перед защитой следует точно назвать опасный приёмник: интерпретатор, файловая система, сетевой клиент, HTML DOM или другой компонент.

2. Инъекции: данные, прочитанные как команда

Каноничный случай — SQL-инъекция: ввод пользователя вклеивается в запрос, завершает задуманное выражение и добавляет новое. Надёжная основа защиты — параметризованный запрос, в котором структура команды передаётся отдельно от значений. Параметры обычно нельзя использовать вместо имён таблиц, столбцов и направления сортировки; такие динамические части выбирают из небольшого списка заранее заданных вариантов. ORM и хранимая процедура тоже не защищают от инъекции, если внутри снова собирают SQL строкой. Учётной записи приложения дают только необходимые права, чтобы ограничить последствия оставшейся ошибки.

У команд ОС тот же корень, но лучшая защита — вообще не вызывать оболочку: запустить нужную программу API-функцией с отдельным массивом аргументов и минимальными правами. Для LDAP, NoSQL и шаблонизаторов используют структурные API и правила конкретного языка. Если структуру приходится выбирать по пользовательскому вводу, его сопоставляют с разрешённым вариантом, а не вставляют как произвольный текст. Диагностический вопрос остаётся прежним: какой компонент получит значение и в каком контексте его разберёт?

3. XSS: инъекция, исполненная в браузере

Межсайтовый скриптинг (XSS) возникает, когда недоверенное содержимое исполняется в странице с полномочиями её источника. Такой код может менять интерфейс, читать доступные данные DOM и ответы того же источника, отправлять запросы от имени пользователя и похищать значения, доступные JavaScript. Сессионную cookie с HttpOnly он прочитать не сможет, но всё ещё может выполнять действия в текущей сессии. Поэтому HttpOnly уменьшает ущерб, а не устраняет XSS. Связь полномочий со строением браузера и правилом одного источника здесь принципиальна.

По пути внедрения различают хранимый XSS, когда содержимое записано и показывается другим; отражённый, когда сервер возвращает часть запроса; и DOM-based XSS, когда опасное преобразование выполняет клиентский код. Риск определяется не названием вида, а числом затронутых пользователей, доступными данными и действиями.

Основная защита — безопасный способ построения DOM и контекстное кодирование вывода. В HTML-текст вставляют текст, а не разметку; в клиентском коде предпочитают textContent и безопасные свойства опасным приёмникам вроде innerHTML. Автоматическое экранирование шаблонизатора оставляют включённым, но не переносят его результат между HTML, атрибутом, URL, CSS и JavaScript так, будто контексты одинаковы. Недоверенное значение не помещают в исполняемый JavaScript. Если пользователю действительно разрешён богатый HTML, его обрабатывают поддерживаемым санитайзером с явным списком допустимых элементов, атрибутов и схем URL.

4. CSP: рубеж на случай провала

Content Security Policy (CSP) — дополнительный рубеж: заголовок задаёт, какие скрипты и другие ресурсы браузер может загрузить и исполнить. Но широкая политика со списком доверенных доменов нередко разрешает слишком много, а unsafe-inline или unsafe-eval возвращают опасные способы исполнения.

Строгая политика разрешает скрипты по непредсказуемому одноразовому nonce либо по хэшу, запрещает плагины директивой object-src 'none' и ограничивает базовый URL через base-uri 'none'. Nonce создают заново для каждого ответа и ставят только на доверенные элементы; пользовательский текст не должен получить возможность скопировать его в новый скрипт. Для динамически загружаемого кода применяют strict-dynamic с учётом поддержки браузеров.

CSP сначала разворачивают в режиме Content-Security-Policy-Report-Only, анализируют нарушения и только затем включают блокировку. Отчёты сами могут содержать чувствительные фрагменты URL, поэтому их собирают и хранят осмотрительно. Даже строгая CSP не заменяет безопасные DOM API, контекстное кодирование и санитизацию: она уменьшает вероятность исполнения и последствия пропущенной ошибки, но не исправляет саму инъекцию.

5. CSRF: злоупотребление доверием к вам

Здесь корень другой — путаница границы доверия. Браузер может отправить запрос на один сайт со страницы другого: форма, ссылка и некоторые загрузки не требуют разрешения CORS. Если правила cookie допускают, браузер автоматически приложит сессионную cookie. Правило одного источника обычно не позволит чужой странице прочитать ответ, но само изменение уже может состояться. Межсайтовая подделка запроса (CSRF) заставляет вошедшего пользователя выполнить нежелательное действие именно через такие автоматически предъявляемые учётные данные.

Операции чтения не должны менять состояние. Для изменяющих запросов сервер проверяет непредсказуемый CSRF-токен, связанный с сессией или запросом и не попадающий в URL. Чужой источник не может прочитать его с защищаемого сайта. Дополнительные сигналы — заголовки Origin или Referer и Fetch Metadata; их проверяют по явному списку ожидаемых источников. CORS сам по себе защитой от CSRF не является.

SameSite ограничивает отправку cookie в межсайтовом контексте и служит сильным дополнительным рубежом, но его поведение зависит от режима и способа навигации. Понятия site и origin различаются, поэтому уязвимый соседний поддомен тоже важен. Схемы входа и интеграции, которым необходим SameSite=None, особенно нуждаются в токене и проверке источника. XSS на самом защищаемом источнике обычно способен прочитать CSRF-токен и выполнить действие, поэтому эти классы защиты не заменяют друг друга.

6. Куки и их атрибуты

Cookie часто содержит идентификатор сессии из статьи об аутентификации и авторизации. Атрибуты cookie задают условия автоматической отправки:

  • HttpOnly запрещает JavaScript читать cookie. Для сессионного идентификатора его обычно включают. Атрибут препятствует прямой краже значения через XSS, но вредоносный код всё ещё может посылать запросы из открытой страницы.
  • Secure разрешает отправку только по HTTPS. Весь сайт и все точки установки cookie при этом должны постоянно использовать TLS; полезно дополнить его HSTS.
  • SameSite задают явно. Strict наиболее ограничителен, Lax допускает некоторые переходы верхнего уровня, None нужен для действительно межсайтового использования и требует Secure. Режим выбирают по потоку приложения, а не по максимальной строгости без тестирования.
  • Domain и Path ограничивают область отправки. Без Domain cookie остаётся привязанной к установившему её хосту, что обычно безопаснее допуска всех поддоменов. Path помогает маршрутизации, но не является границей безопасности: приложения одного источника могут влиять друг на друга.
  • Срок ограничивают потребностью сессии; выход и серверный отзыв должны прекращать доступ даже до истечения браузерной cookie. Префикс имени __Host- при Secure, Path=/ и отсутствии Domain позволяет браузеру отклонить настройку с более широкой областью Domain или Path.

Для типичной сессии исходная точка — host-only cookie с HttpOnly, Secure, явным SameSite и коротким обоснованным сроком. Затем настройки проверяют во всех потоках входа, перенаправления и выхода. Атрибуты сужают условия кражи и нежелательной отправки, но не исправляют XSS, CSRF или ошибку серверной авторизации.

7. Что ещё держать на радаре

Полный обзор шире одной статьи; этот список обозначает соседние риски и основные направления защиты.

  • Заголовки безопасности. HSTS закрепляет HTTPS; X-Content-Type-Options: nosniff запрещает угадывать тип; Referrer-Policy ограничивает утечку адресов; директива CSP frame-ancestors защищает от встраивания страницы и части атак через подмену интерфейса. Набор выбирают и тестируют для конкретного приложения.
  • Открытые перенаправления. Произвольный адрес из параметра превращает доверенный домен в начало фишинговой ссылки и может нарушить протокол входа. Разрешают локальный путь либо точный список назначений, а не проверяют совпадение подстроки.
  • SSRF. Если сервер загружает URL из ввода, атакующий пытается обратиться к внутренним службам, служебным метаданным и неожиданным протоколам. URL разбирают стандартным парсером, разрешают только необходимые схемы, хосты и порты, а все A- и AAAA-адреса после разрешения имени сверяют с сетевой политикой. Соединение устанавливают именно с проверенным адресом либо иным способом исключают повторное неконтролируемое DNS-разрешение между проверкой и подключением. Перенаправления отключают либо повторяют проверку на каждом шаге. Сетевые правила исходящего трафика ограничивают достижимые адреса вторым независимым рубежом. Один запрет строк вида 127.0.0.1 и 192.168.* обходится альтернативной записью IP-адреса, DNS и перенаправлением.
  • Утечки в ошибках и журналах. Наружу отдают стабильный код и безопасное описание, подробности связывают с внутренним идентификатором события. Пароли, токены и содержимое cookie не журналируют; персональные данные сводят к необходимому минимуму и защищают. Подробнее это рассматривает статья об обработке ошибок.
  • Загрузка файлов. Размер, имя, расширение, заявленный и фактический тип считаются недоверенными. Файлу дают новое имя, хранят вне исполняемой области, отдают с безопасным типом и правами, а обработчики изображений и документов изолируют и обновляют.
  • Зависимости. Уязвимый или подменённый компонент может работать и на сервере, и в браузере. Инвентаризация, фиксация входов и защищённая сборка разобраны в статье «Цепочка поставок ПО».

8. Практикум: атаковать и починить

Используйте намеренно уязвимое учебное приложение, например OWASP Juice Shop, или маленький локальный стенд. Запускайте его без доступа к рабочим данным и секретам, не публикуйте в Интернет и ограничьте исходящую сеть. Все пользователи, cookie, адреса и внутренние службы в заданиях должны быть вымышленными. Чужие системы проверять нельзя.

Задание 1. SQL-инъекция и параметризация. На стенде с конкатенацией запроса покажите, что специально составленное значение меняет условие SQL. Затем перейдите на параметризованный запрос и убедитесь, что то же значение обрабатывается как данные. Добавьте тест, чтобы ошибка не вернулась, и объясните, почему фильтрация отдельных символов слабее структурного разделения.

Задание 2. XSS и границы HttpOnly. Создайте отдельную тестовую cookie-маркер без HttpOnly и вставьте в учебный комментарий скрипт, который выводит доступные ему cookie на этой же странице, никуда их не отправляя. Включите HttpOnly у маркера и подтвердите, что значение скрыто, но скрипт всё ещё меняет страницу и может вызвать действие. Затем замените опасную вставку на контекстно безопасный вывод и убедитесь, что код перестал исполняться.

Задание 3. CSP как дополнительный рубеж. Для оставленной на стенде точки инъекции сначала включите строгую политику в режиме Report-Only. Устраните нарушения собственного кода, создавайте новый nonce на ответ и включите блокировку. Проверьте, что инъекция не исполняется, а затем всё равно исправьте её первопричину.

Задание 4. CSRF на двух локальных сайтах. Разместите защищаемый стенд и страницу-инициатор на разных тестовых сайтах и источниках, например с именами app.test и attacker.test, направленными на локальную машину. Разные порты одного имени дают разные источники, но могут остаться одним сайтом для SameSite. Под тестовой записью покажите изменение без токена, затем добавьте серверную проверку CSRF-токена и ожидаемого Origin. Отдельно меняйте SameSite и тип запроса, записывая фактическое поведение браузера.

Задание 5. Аудит cookie. На тестовом контуре составьте таблицу для каждой cookie: назначение, HttpOnly, Secure, SameSite, Domain, Path и срок. Проверьте вход, межсайтовый переход, выход и истечение сессии. Каждое отличие от ожидаемой политики оформите как конкретную задачу, а не исправляйте в рабочей системе без проверки потока.

Задание 6*. Безопасная модель SSRF. Поднимите внутри изолированной сети безвредную службу, возвращающую слово-маркер, и уязвимый загрузчик URL. Не обращайтесь к настоящему облачному адресу метаданных. Покажите доступ к службе, затем введите список разрешённых назначений, повторную проверку перенаправления и ограничение исходящей сети. Убедитесь, что разрешённый учебный адрес работает, а внутренний и перенаправление на него блокируются.

Итоги

  • Смешение данных с командами и неверные границы доверия объясняют многие, но не все веб-уязвимости. Сначала определяют компонент, который интерпретирует ввод, и полномочия, с которыми он работает.
  • SQL и другие инъекции предотвращают структурными API и параметрами; динамические части выбирают из разрешённых вариантов. Кодирование применяется для конкретного контекста, а минимальные права ограничивают последствия.
  • При XSS недоверенный код получает полномочия страницы. Безопасные DOM API, контекстный вывод и санитайзер устраняют первопричину; HttpOnly лишь скрывает cookie от чтения.
  • Строгая CSP с nonce или хэшами — дополнительный рубеж. Её разворачивают через Report-Only и не считают заменой исправлению инъекций.
  • CSRF использует автоматически отправляемую сессию. Защита сочетает безопасную семантику методов, CSRF-токен, проверку источника и SameSite.
  • Сессионной cookie обычно нужны HttpOnly, Secure, явный SameSite, узкая область и ограниченный срок. Domain лучше не расширять без необходимости, а Path не считается границей безопасности.
  • SSRF требует проверки разобранного URL и каждого разрешённого адреса, контроля перенаправлений и сетевого ограничения исходящего трафика; простого списка запрещённых строк недостаточно.

Литература

  1. OWASP, Application Security Verification Standard 5.0 — проверяемые требования к безопасности веб-приложений и API.
  2. OWASP Top 10: 2025 — обзор распространённых классов риска; подробные меры приведены в памятках OWASP по XSS, CSRF и SSRF.
  3. W3C, Content Security Policy Level 3; MDN, практическое руководство по CSP.
  4. MDN, Set-Cookie — синтаксис и свойства атрибутов cookie.
  5. OWASP Juice Shop — намеренно уязвимое приложение для изолированного практикума.
  6. Связанные статьи проекта: «Модель угроз», «Как устроен браузер», «Аутентификация и авторизация», «TLS», «Цепочка поставок ПО» и «Как современные языки обращаются с ошибками».
404 Not Found

404 Not Found


nginx/1.24.0 (Ubuntu)

Связь с редакцией