Как предотвратить XSS

В этом разделе мы опишем общие принципы предотвращения уязвимостей межсайтового скриптинга (XSS) и способы использования различных распространённых технологий для защиты от XSS-атак.

Предотвращение XSS в целом достигается за счёт двух уровней защиты:

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

Кодируйте данные при выводе

Кодирование следует применять непосредственно перед тем, как записывать контролируемые пользователем данные на страницу, поскольку контекст, в который вы пишете, определяет требуемый тип кодирования. Например, значения внутри строк JavaScript требуют иного экранирования, чем в HTML‑контексте.

В HTML‑контексте вы должны преобразовывать значения, не попавшие в белый список, в HTML‑сущности:

  • < преобразуется в: &lt;

  • > преобразуется в: &gt;

В строковом контексте JavaScript неалфанумерические значения должны кодироваться в формате Unicode:

  • < преобразуется в: \u003c

  • > преобразуется в: \u003e

Иногда необходимо применять несколько уровней кодирования в правильном порядке. Например, чтобы безопасно встроить пользовательский ввод внутрь обработчика события, нужно учесть и контекст JavaScript, и контекст HTML. Поэтому сначала следует выполнить Unicode‑экранирование ввода, а затем HTML‑кодирование:

<a href="#" onclick="x='This string needs two layers of escaping'">test</a>

Валидируйте ввод при получении

Кодирование, вероятно, является наиболее важной линией защиты от XSS, но его недостаточно, чтобы предотвратить XSS‑уязвимости во всех контекстах. Вы также должны максимально строго валидировать ввод в момент его первоначального получения от пользователя.

Примеры валидации ввода включают:

  • Если пользователь отправляет URL, который будет возвращён в ответах, валидируйте, что он начинается с безопасного протокола, например HTTP или HTTPS. Иначе кто‑то может эксплуатировать ваш сайт с вредоносным протоколом, таким как javascript или data.

  • Если пользователь предоставляет значение, которое ожидается числовым, проверьте, что оно действительно содержит целое число.

  • Валидируйте, что ввод содержит только ожидаемый набор символов.

В идеале валидация ввода должна работать путём блокирования недопустимого ввода. Альтернативный подход — попытка «очистить» недопустимый ввод, чтобы сделать его допустимым — более подвержен ошибкам и его следует избегать, где это возможно.

Белые списки против чёрных списков

Валидация ввода в целом должна использовать белые списки, а не чёрные списки. Например, вместо попытки составить список всех вредоносных протоколов (javascript, data и т.д.), просто составьте список безопасных протоколов (HTTP, HTTPS) и запретите всё, чего нет в списке. Это гарантирует, что ваша защита не «сломается», когда появятся новые вредоносные протоколы, и снизит восприимчивость к атакам, которые пытаются обфусцировать недопустимые значения для обхода чёрного списка.

Разрешение «безопасного» HTML

По возможности следует избегать разрешения пользователям публиковать HTML‑разметку, но иногда это бизнес‑требование. Например, блог может позволять оставлять комментарии, содержащие ограниченный набор HTML‑разметки.

Классический подход — попытаться отфильтровать потенциально опасные теги и JavaScript. Можно попробовать реализовать это с использованием белого списка безопасных тегов и атрибутов, но из‑за различий в движках парсинга браузеров и особенностей вроде mutation XSS этот подход чрезвычайно сложно безопасно реализовать.

Наименее плохой вариант — использовать библиотеку JavaScript, которая выполняет фильтрацию и кодирование в браузере пользователя, такую как DOMPurify. Другие библиотеки позволяют пользователям предоставлять контент в формате markdown и конвертировать его в HTML. К сожалению, у всех этих библиотек время от времени обнаруживаются XSS‑уязвимости, поэтому это не идеальное решение. Если вы используете такую библиотеку, вам следует внимательно следить за обновлениями безопасности.

Note

Помимо JavaScript, иной контент, такой как CSS и даже обычный HTML, в некоторых ситуациях может быть опасным.

Атаки с использованием вредоносного CSS

Как предотвратить XSS с помощью шаблонизатора

Многие современные веб‑сайты используют серверные шаблонизаторы (template engines), такие как Twig и Freemarker, чтобы встраивать динамический контент в HTML. Обычно у них есть собственная система экранирования. Например, в Twig можно использовать фильтр e() с аргументом, определяющим контекст:

Некоторые другие шаблонизаторы, такие как Jinja и React, по умолчанию экранируют динамический контент, что эффективно предотвращает большинство случаев XSS. Мы рекомендуем внимательно изучать возможности экранирования при оценке целесообразности использования конкретного шаблонизатора или фреймворка.

Note

Если вы напрямую конкатенируете пользовательский ввод в шаблонные строки, вы будете уязвимы к инъекции серверных шаблонов (server-side template injection), которая часто серьёзнее, чем XSS.

Как предотвратить XSS в PHP

В PHP есть встроенная функция кодирования сущностей под названием htmlentities. Вы должны вызывать эту функцию для экранирования ввода в HTML‑контексте. Функцию следует вызывать с тремя аргументами:

  • Ваша входная строка.

  • ENT_QUOTES — флаг, указывающий, что все кавычки должны быть закодированы.

  • Набор символов, который в большинстве случаев должен быть UTF-8.

Например:

В строковом контексте JavaScript вам нужно кодировать ввод в Unicode, как описано выше. К сожалению, PHP не предоставляет API для Unicode‑экранирования строки. Ниже приведён код на PHP для этого:

Вот как использовать функцию jsEscape в PHP:

В качестве альтернативы вы можете использовать шаблонизатор.

Как предотвратить XSS на стороне клиента в JavaScript

Чтобы экранировать пользовательский ввод в HTML‑контексте на JavaScript, вам нужен собственный HTML‑энкодер, поскольку JavaScript не предоставляет API для кодирования HTML. Вот пример кода JavaScript, который преобразует строку в HTML‑сущности:

Далее вы бы использовали эту функцию так:

Если ваш ввод находится внутри строки JavaScript, вам нужен энкодер, выполняющий Unicode‑экранирование. Вот пример такого энкодера:

Далее вы бы использовали эту функцию так:

Как предотвратить XSS в jQuery

Наиболее распространённая форма XSS в jQuery — когда вы передаёте пользовательский ввод в селектор jQuery. Веб‑разработчики часто использовали location.hash и передавали его в селектор, что приводило к XSS, поскольку jQuery рендерил HTML. jQuery распознал проблему и исправил логику селектора, чтобы проверять, начинается ли ввод с «хеша». Теперь jQuery отрендерит HTML только если первый символ — <. Если вы передаёте недоверенные данные в селектор jQuery, убедитесь, что вы правильно экранировали значение с помощью функции jsEscape, приведённой выше.

Смягчение XSS с помощью Content Security Policy (CSP)

Политика безопасности контента (CSP) — это последняя линия обороны против XSS. Если ваши меры предотвращения XSS дали сбой, вы можете использовать CSP для смягчения XSS, ограничив то, что может сделать атакующий.

CSP позволяет контролировать различные вещи, например, можно ли загружать внешние скрипты и будут ли выполняться встроенные (inline) скрипты. Чтобы задействовать CSP, нужно включить HTTP‑заголовок ответа Content-Security-Policy со значением, содержащим вашу политику.

Пример CSP выглядит так:

Эта политика указывает, что ресурсы, такие как изображения и скрипты, можно загружать только с того же источника, что и основная страница. Поэтому даже если атакующий успешно инжектирует XSS‑пейлоад, он сможет загружать ресурсы только с текущего origin. Это существенно снижает вероятность того, что атакующий сможет эксплуатировать XSS‑уязвимость.

Если вам необходимо загружать внешние ресурсы, убедитесь, что вы разрешаете только такие скрипты, которые не помогут атакующему эксплуатировать ваш сайт. Например, если вы помещаете домены в белый список, атакующий сможет загружать любые скрипты с этих доменов. Где возможно, старайтесь размещать ресурсы на вашем собственном домене.

Если это невозможно, вы можете использовать политику на основе хэшей или nonce, чтобы разрешать скрипты на разных доменах. Nonce — это случайная строка, добавляемая как атрибут скрипта или ресурса, который будет выполнен только если случайная строка совпадает с сгенерированной сервером. Атакующий не сможет угадать рандомизированную строку и, следовательно, не сможет вызвать скрипт или ресурс с валидным nonce, поэтому ресурс не будет выполнен.

Last updated