Cross-Site Scripting (XSS): векторы атак и защита
Почему XSS до сих пор преследует веб-приложения
Межсайтовый скриптинг (XSS) остаётся одной из самых распространённых веб-уязвимостей. Несмотря на широкую осведомлённость, он неизменно присутствует в OWASP Top 10. Корень проблемы: приложения доверяют пользовательскому вводу и отображают его без должной обработки. Злоумышленники внедряют вредоносные скрипты, которые выполняются в браузерах жертв, что приводит к перехвату сессий, краже данных и дефейсу.
В этой статье разбираются векторы XSS-атак и предлагаются практические меры защиты, которые можно внедрить уже сегодня.
Что такое XSS и как это работает?
XSS возникает, когда приложение включает недоверенные данные в веб-страницу без проверки или кодирования. Браузер затем выполняет внедрённый скрипт, как если бы он был частью легитимного сайта. Поскольку скрипт выполняется в контексте уязвимого сайта, он может получить доступ к cookie, локальному хранилищу и выполнять запросы от имени пользователя.
Рассмотрим простую страницу поиска, которая отражает запрос:
<?php echo 'You searched for: ' . $_GET['q']; ?>
Если злоумышленник создаст URL вида search.php?q=<script>alert('XSS')</script>, скрипт выполнится, когда жертва перейдёт по ссылке.
Три основных типа XSS
1. Отражённый XSS
Вредоносный скрипт является частью запроса (например, параметра URL) и немедленно отражается в ответе. Жертву нужно обманом заставить кликнуть по ссылке или отправить форму. Часто используется в фишинговых кампаниях.
2. Хранимый XSS
Скрипт постоянно хранится на сервере (например, в базе данных, поле комментария или профиле пользователя). Каждый посетитель заражённой страницы выполняет скрипт. Хранимый XSS опаснее, так как не требует никаких действий, кроме просмотра страницы.
3. DOM-based XSS
Уязвимость существует в клиентском JavaScript, который читает данные из недоверенного источника (например, location.hash) и записывает их в DOM без безопасной обработки. Сервер может никогда не увидеть вредоносную нагрузку.
document.getElementById('output').innerHTML = location.hash.substring(1);
Если хэш содержит <img src=x>, скрипт выполнится.
Распространённые векторы атак
- Незкранированный пользовательский ввод в HTML: прямое внедрение в HTML-содержимое, атрибуты или JavaScript.
- Неправильное использование опасных приёмников: функции вроде
innerHTML,document.write,evalиsetTimeoutсо строковыми аргументами. - Внедрение через URL: манипуляции с атрибутами
href,srcилиstyleс URIjavascript:. - Сторонние компоненты: уязвимые библиотеки или виджеты, отображающие недоверенные данные.
Защита от XSS
1. Кодирование вывода (контекстное экранирование)
Кодируйте все недоверенные данные перед выводом в HTML, атрибуты, JavaScript, CSS или URL. Используйте кодирование, соответствующее контексту:
- HTML-сущности: преобразуйте
&,<,>,",'в сущности. - JavaScript-кодирование: экранируйте небуквенно-цифровые символы в Unicode.
- URL-кодирование: используйте
encodeURIComponent()для параметров запроса.
Большинство современных фреймворков (React, Angular, Vue) автоматически экранируют по умолчанию, но будьте осторожны при использовании dangerouslySetInnerHTML или аналогичных обходных путей.
2. Валидация и санитизация ввода
Проверяйте ввод на стороне сервера с помощью белых списков. Для форматированного текста используйте библиотеку вроде DOMPurify для санитизации HTML, удаляя опасные теги и атрибуты.
const clean = DOMPurify.sanitize(userInput);
Никогда не полагайтесь только на клиентскую валидацию.
3. Content Security Policy (CSP)
CSP — мощный механизм эшелонированной защиты. Он ограничивает источники исполняемых скриптов, встроенных скриптов и других ресурсов. Строгая CSP может заблокировать XSS, даже если внедрение произошло.
Content-Security-Policy: default-src 'self'; script-src 'self' https://trusted.cdn.com; object-src 'none';
Избегайте unsafe-inline и unsafe-eval. Используйте nonce или хэши для легитимных встроенных скриптов.
4. Безопасные атрибуты cookie
Устанавливайте cookie с атрибутами HttpOnly (запрещает доступ из JavaScript) и Secure (только HTTPS). Это смягчает перехват сессий через XSS.
5. Используйте современные фреймворки и избегайте опасных API
Фреймворки вроде React, Angular и Vue автоматически экранируют данные. Избегайте прямого манипулирования DOM через innerHTML. Если необходимо вставить HTML, сначала санитизируйте его.
6. Регулярное тестирование безопасности
Внедрите SAST, DAST и ручное тестирование на проникновение. Инструменты вроде OWASP ZAP помогают выявлять XSS-уязвимости.
Сравнение типов XSS и основных мер защиты
| Тип XSS | Описание | Основная защита |
|---|---|---|
| Отражённый | Нагрузка в запросе, отражается в ответе | Кодирование вывода, валидация ввода |
| Хранимый | Нагрузка хранится на сервере, отдаётся всем пользователям | Санитизация, кодирование вывода, CSP |
| DOM-based | Клиентское внедрение через небезопасные DOM API | Безопасные DOM API, CSP, отказ от eval |
Пошагово: внедрение защиты от XSS
- Определите все точки ввода: формы, параметры URL, заголовки, cookie.
- Примените контекстное кодирование вывода: используйте встроенные функции или библиотеки вроде OWASP Java Encoder.
- Санитизируйте форматированный текст: используйте DOMPurify или аналоги.
- Разверните CSP: начните с режима report-only, затем включите принудительно.
- Установите атрибуты HttpOnly и Secure для cookie.
- Обучайте разработчиков: тренируйте безопасным практикам кодирования.
- Тестируйте регулярно: интегрируйте тестирование безопасности в CI/CD.
FAQ
В чём разница между XSS и CSRF?
XSS выполняет вредоносные скрипты в браузере жертвы, а CSRF заставляет браузер отправлять несанкционированные запросы на сайт, где пользователь аутентифицирован. XSS можно использовать для обхода защит CSRF.
Может ли Content Security Policy полностью предотвратить XSS?
Нет, CSP — это мера эшелонированной защиты. Она значительно снижает риск, но не может заменить правильное кодирование вывода и валидацию ввода. Неправильно настроенная CSP может всё ещё допускать некоторые атаки.
Достаточно ли клиентской валидации для предотвращения XSS?
Нет. Клиентскую валидацию легко обойти. Всегда проверяйте и кодируйте на стороне сервера и рассматривайте все клиентские данные как недоверенные.
Заключение
XSS — это постоянная угроза, но с многоуровневой стратегией защиты — кодирование вывода, валидация ввода, CSP и безопасные cookie — вы можете эффективно её смягчить. Будьте бдительны, обновляйте фреймворки и тестируйте непрерывно.
Для дополнительных инструментов безопасности ознакомьтесь с нашим Nginx Log Analyzer для обнаружения подозрительных шаблонов в серверных логах.