Проект ЦИТадель
Веб-приложение постоянно работает с клиентом, которого не контролирует. Пользователь — а значит, и атакующий — выбирает адреса, параметры, заголовки, содержимое форм и последовательность запросов. В ответ сервер отправляет браузеру разметку и код, которые обрабатываются с полномочиями сайта и в контексте действующей сессии пользователя. Ошибка в том, как приложение истолковало ввод или кому доверило действие, может превратить обычную строку в команду, страницу — в средство кражи данных, а законный вход — в инструмент атаки.
Внешне SQL-инъекция, XSS, CSRF и SSRF устроены по-разному, но многие классические уязвимости объясняются двумя повторяющимися ошибками. Первая — смешение данных и команд, когда недоверенное значение попадает в контекст, где его исполняют. Вторая — неверная граница доверия, когда сервер принимает запрос, источник или сетевое назначение без необходимой проверки. Такое объяснение позволяет не только узнать атаку по имени, но и вывести защиту из её механизма.
Сначала статья разбирает инъекции на сервере и в браузере, контекстно безопасный вывод и CSP. Затем — межсайтовые запросы, атрибуты cookie и другие границы веб-приложения, включая SSRF, загрузку файлов и внешние зависимости. В завершение предлагается практикум, в котором каждая уязвимость сначала воспроизводится на изолированном стенде, а затем устраняется и закрепляется проверкой.
Байты становятся данными или командами в зависимости от того, как их использует интерпретатор. Инъекция возникает, когда недоверенное значение попадает в командный контекст: в текст SQL-запроса, команду оболочки, шаблон или исполняемую разметку. Основная защита — сохранять структурную границу: передавать значения через параметры и безопасные API, а вывод кодировать для конкретного контекста. Правила для HTML-текста, атрибута, URL, JavaScript, SQL и оболочки различаются; универсального «экранирования опасных символов» нет.
Не всякое использование строки является исполнением кода. Например, обход каталогов через ../ заставляет файловый API открыть не тот ресурс, но не превращает путь в программу. Здесь нужны нормализация, привязка результата к разрешённому каталогу и правила доступа. Поэтому перед защитой следует точно назвать опасный приёмник: интерпретатор, файловая система, сетевой клиент, HTML DOM или другой компонент.
Каноничный случай — SQL-инъекция: ввод пользователя вклеивается в запрос, завершает задуманное выражение и добавляет новое. Надёжная основа защиты — параметризованный запрос, в котором структура команды передаётся отдельно от значений. Параметры обычно нельзя использовать вместо имён таблиц, столбцов и направления сортировки; такие динамические части выбирают из небольшого списка заранее заданных вариантов. ORM и хранимая процедура тоже не защищают от инъекции, если внутри снова собирают SQL строкой. Учётной записи приложения дают только необходимые права, чтобы ограничить последствия оставшейся ошибки.
У команд ОС тот же корень, но лучшая защита — вообще не вызывать оболочку: запустить нужную программу API-функцией с отдельным массивом аргументов и минимальными правами. Для LDAP, NoSQL и шаблонизаторов используют структурные API и правила конкретного языка. Если структуру приходится выбирать по пользовательскому вводу, его сопоставляют с разрешённым вариантом, а не вставляют как произвольный текст. Диагностический вопрос остаётся прежним: какой компонент получит значение и в каком контексте его разберёт?
Межсайтовый скриптинг (XSS) возникает, когда недоверенное содержимое исполняется в странице с полномочиями её источника. Такой код может менять интерфейс, читать доступные данные DOM и ответы того же источника, отправлять запросы от имени пользователя и похищать значения, доступные JavaScript. Сессионную cookie с HttpOnly он прочитать не сможет, но всё ещё может выполнять действия в текущей сессии. Поэтому HttpOnly уменьшает ущерб, а не устраняет XSS. Связь полномочий со строением браузера и правилом одного источника здесь принципиальна.
По пути внедрения различают хранимый XSS, когда содержимое записано и показывается другим; отражённый, когда сервер возвращает часть запроса; и DOM-based XSS, когда опасное преобразование выполняет клиентский код. Риск определяется не названием вида, а числом затронутых пользователей, доступными данными и действиями.
Основная защита — безопасный способ построения DOM и контекстное кодирование вывода. В HTML-текст вставляют текст, а не разметку; в клиентском коде предпочитают textContent и безопасные свойства опасным приёмникам вроде innerHTML. Автоматическое экранирование шаблонизатора оставляют включённым, но не переносят его результат между HTML, атрибутом, URL, CSS и JavaScript так, будто контексты одинаковы. Недоверенное значение не помещают в исполняемый JavaScript. Если пользователю действительно разрешён богатый HTML, его обрабатывают поддерживаемым санитайзером с явным списком допустимых элементов, атрибутов и схем URL.
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, контекстное кодирование и санитизацию: она уменьшает вероятность исполнения и последствия пропущенной ошибки, но не исправляет саму инъекцию.
Здесь корень другой — путаница границы доверия. Браузер может отправить запрос на один сайт со страницы другого: форма, ссылка и некоторые загрузки не требуют разрешения CORS. Если правила cookie допускают, браузер автоматически приложит сессионную cookie. Правило одного источника обычно не позволит чужой странице прочитать ответ, но само изменение уже может состояться. Межсайтовая подделка запроса (CSRF) заставляет вошедшего пользователя выполнить нежелательное действие именно через такие автоматически предъявляемые учётные данные.
Операции чтения не должны менять состояние. Для изменяющих запросов сервер проверяет непредсказуемый CSRF-токен, связанный с сессией или запросом и не попадающий в URL. Чужой источник не может прочитать его с защищаемого сайта. Дополнительные сигналы — заголовки Origin или Referer и Fetch Metadata; их проверяют по явному списку ожидаемых источников. CORS сам по себе защитой от CSRF не является.
SameSite ограничивает отправку cookie в межсайтовом контексте и служит сильным дополнительным рубежом, но его поведение зависит от режима и способа навигации. Понятия site и origin различаются, поэтому уязвимый соседний поддомен тоже важен. Схемы входа и интеграции, которым необходим SameSite=None, особенно нуждаются в токене и проверке источника. XSS на самом защищаемом источнике обычно способен прочитать CSRF-токен и выполнить действие, поэтому эти классы защиты не заменяют друг друга.
Cookie часто содержит идентификатор сессии из статьи об аутентификации и авторизации. Атрибуты cookie задают условия автоматической отправки:
Для типичной сессии исходная точка — host-only cookie с HttpOnly, Secure, явным SameSite и коротким обоснованным сроком. Затем настройки проверяют во всех потоках входа, перенаправления и выхода. Атрибуты сужают условия кражи и нежелательной отправки, но не исправляют XSS, CSRF или ошибку серверной авторизации.
Полный обзор шире одной статьи; этот список обозначает соседние риски и основные направления защиты.
Используйте намеренно уязвимое учебное приложение, например 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. Не обращайтесь к настоящему облачному адресу метаданных. Покажите доступ к службе, затем введите список разрешённых назначений, повторную проверку перенаправления и ограничение исходящей сети. Убедитесь, что разрешённый учебный адрес работает, а внутренний и перенаправление на него блокируются.