03.02.2013
By oxdef
In webapps .
tags: ][akep
Введение
Веб-приложения становятся одной из основных частей "цифровой жизни" современного
человека. Кажется, что осталось чуть-чуть и мы вовсе забудем о традиционных приложениях
— останется только смесь из веб-браузера и операционной системы. Уже сейчас
веб-аналоги заменили большинство десктопных приложений: кино и тв, почта, чат, игры,
социальные сети.
С технической точки зрения веб-приложения быстро эволюционируют от "фреймов с
CGI-скриптами" до мощного стека из AJAX, CSS, HTML5 с перемещением большей части
логики на сторону веб-клиента. С точки зрения безопасности это несёт большое
количество проблем для автоматизированного тестирования. Рассмотрим некоторые из
них и попробуем найти решения.
Проблемы
Как я уже отметил выше, за последние годы всё, что связано с вебом, шагнуло
очень далеко вперёд, а веб-браузеры становятся по сути главными
десктоп-приложениями. В тоже время сканеры безопасности веб-приложений
развиваются не так быстро.
На текущий момент процесс сканирования веб-приложения классическим сканером
включает в себя следующие фазы:
Авторизация в тестируемом веб-приложении. Обычно это один или несколько
HTTP-запросов (так называемая "логин-последовательность"), отправка
приложению которых приводит к созданию полноценной пользовательской сессии.
Например, в простейшем случае это может быть POST-запрос с именем
пользователя и паролем на URL вида http://example.com/auth.php
Кровлинг (англ. "crawling"). По сути, это обход с определенной глубиной "пауком"
веб-приложения и сбор всех сущностей, которые могут
генерировать HTTP-запросы: ссылки, формы, подключение скриптов и т.д.
"Фаззинг" (англ. "fuzzing") — тот самый этап, на котором сканер и шлёт большое количество HTTP-запросов
с магической нагрузкой , анализирует ответы веб-сервера и пытается
определить наличие XSS, SQLi и прочих уязвимостей.
Отчёт - наверно, самая простая фаза, когда сканер генерирует в необходимом формате отчёт для пользователя.
К несчастью, когда имеешь дело с современным веб-приложением, у обычного
сканера "обламываются зубы" при попытке найти в нем уязвимости. Проблемы
начинаются уже на первом этапе "кровлинга". Когда сканер запрашивает веб-страницу,
он надеется получить в итоге пусть и не "валидный", но всё-таки преимущественно HTML с примесью
JavaScript и CSS. Затем со страницы аккуратно выдираются ссылки, формы и т.п. Но
в мире Веб 2.0 index.html уже давно не тор^Cт! Сейчас это уже скорее мясо из
JavaScript с лёгкой окантовкой из HTML (в том числе и для того, что бы сообщить
параноик^Cпользователю о необходимости включить JavaScript). Посмотрите на HTML-исходник любого
популярного социального сервиса и убедитесь в этом. Весь пользовательский
интерфейс строится сейчас на клиенте. Сканер в HTTP-ответе не видит никаких
ссылок, форм. "Кровлинг" проходит практически безрезультатно. А всё потому, что
в то время, как веб-браузеры эволюционировали от таких представителей, как Lynx и Mosaic до
монстров Firefox и Chrome. В тоже время сканеры (в большинстве своём) внутри
остались на уровне текстовых браузеров в плане возможностей.
Другой блок проблем — это коммуникация между веб-клиентом и веб-сервером.
Конечно, как и в эре 1.0, это до сих пор в большинстве случаев делается с помощью
стандартных GET/POST-запросов. В тоже время приходится учитывать и такие моменты
как:
в каком виде данные путешествуют между сторонами? В виде GET-параметров? Как
POST-нагрузка?
а может мы имеем дело с XML- или JSON-данными
а как насчёт поддержки веб-сокетов?
И наконец, конечно же обычный сканер мало что может предпринять для поиска чисто
клиентских проблем, например DOM-based XSS . Да, разработчики сканеров
пытаются выкрутиться из положения и используют "умный" поиск сигнатур возможной
уязвимости в JavaScript. Но всё же очевидно, что для их детектирования
сканеру необходим полноценный встроенный JavaScript-движок, ровно как и другие
браузерные технологии. И это очень непростая задача для разработчиков.
Сравнение сканеров уязвимостей веб-приложений
Для того, что бы выяснить текущее положение с поддержкой современных
веб-технологий в сканерах уязвимостей я подготовил (ну хорошо, взял старое и
переделал в духе веб 2.0) специальное тестовое
веб-приложение под названием Itter. Да-да, это сервис микроблогов и я надеюсь,
что он ста^Cне станет таким же популярным как и его практически однофамилец :)
Тестовое приложение обладает следующими характеристиками:
LAMP (Linux-Apache-MySQL-PHP)
поиск, личные сообщения, закладки
пользовательская часть за аутентификацией
использование AJAX (например, для закладок)
ну конечно же тут есть уязвимости ;)
Затем я отправился на всем известный сайт sectools.org и взял несколько сканеров из секции Web
Scanners . Выбор делался на основе следующих критериев:
встроенный модуль "кровлинга" (по этой причине утилиты вроде
"старого-лампового" nikto не попали в выборку)
в случае с коммерческими продуктами доступная демо или триал версия
Результаты тестирования были, вообще говоря, предсказуемыми.
Сравнения сканеров
Сканер
DOM-based XSS
Нашёл AJAX-запросы
Комментарий
w3af (trunk version)
Вообще-то нет, но есть спец. grep-плагин для поиска опасных участков JavaScript-кода, в т.ч. и domXSS
Нет
Skipfish (2.05b-1)
Нет
Нет
Поддержка JavaScript в TODO
wapiti (1.1.6)
Нет
Нет
BurpProxy (1.4.01)
Нет
Нет
Протестирован Spider из бесплатной версии
ZapProxy (1.3.2)
Нет
Нет
Использовались spider+attack modules
Acunetix (8.0)
Да
Да
У Акунетикса есть технология под названием CSA (и возможность использовать модуль MS IE как встроенные браузер)
Очень небольшое количество сканеров из протестированных имеют хоть какую-нибудь поддержку
AJAX-технологий. Q.E.D
Теперь давайте обсудим возможные пути и просто костыли для решения этих
сложных задач.
Решения
Хорошо, мы убедились, что AJAX и вообще современные веб-технологии
принесли немало проблем для сканеров уязвимостей. Эти проблемы можно разделить
на две большие области:
сбор HTTP-трафика между клиентской и серверной частями веб-приложения
детектирование проблем безопасности, специфичных для клиентской стороны.
Но что у нас с возможными решениями? Попробуем разобраться и рассмотреть
следующие варианты:
Специальный JavaScipt-парсер для извлечения URL-адресов из кусов JavaScript-кода
Классическое сканирование с подмешиванием сохранённых HTTP-запросов
Интеграция с уже существующими QA-инструментами, например Selenium
Встроенный движок браузера с магией для имитации действий пользователя
Специально подготовленное API веб-приложения для взаимодействия со сканером
Первый вариант представляет по сути своей JavaScript-парсер (обычно
реализованный с помощью механизма регулярных выражений) для поиска опасных
участков кода и извлечения ценной информации вроде тех же URL-адресов.
Недостаток такого варианта очевиден - это в любом случае не полноценный
JavaScript-движок, а значит толку от него в силу событийной природы языка будет не много.
Второй вариант проще. Ведь и правда, можно предположить, что нам
удалось откуда-то достать HTTP-транзакции тестового веб-приложения (например,
распарсить логи веб-сервера) и их можно импортировать в сканер для дальнейшего
фаззинга. А уж функция импорта есть сейчас практически во всех сканерах!
Но помимо необходимости специального окружения для регулярных сканирований, этот
вариант не годится ещё и в силу неумения детектировать клиентские уязвимости.
Да и вообще выглядит уж сильно ограниченно.
Помимо тестирования безопасности веб-приложения нуждаются и в обычном
функциональном тестировании. А это значит, что у тестировщиков уже наверняка
есть инструменты для автоматизации этого процесса в рамках SDLC . Одно из самых
популярных решений для функционального тестирования интерфейсов веб-приложений — это Selenium .
Логичный вопрос — можем ли использовать его для тестирования безопасности
веб-приложения? Для начала можно сделать единый прокси и проводить через него
весь трафик от тестировщиков :) Таким образом мы заполучим так необходимые сканеру
HTTP-запросы веб-приложения. Но, имея только трафик, мы столкнёмся с теми же
проблемами других вариантов, что рассмотрены выше. Помимо прочего существуют
также исследования на тему использования Селениума напрямую для проверок
безопасности , .
Четвёртый вариант предусматривает наличие встроенного движка современного
веб-браузера (например, Microsoft IE или WebKit) плюс полноценный JavaScript-движок.
На этом варианте мы остановимся чуть подробнее далее.
И немного слов о последнем варианте. Мы можем представить серверную часть
современного веб-приложения как набор API-методов доступных по HTTP-протоколу.
Таким образом, если мы рассматриваем автоматизированное сканирование как часть
цикла разработки ПО, то мы можем договориться с разработчиками о том, что бы
предоставить сканеру специальный манифест, описывающий это API. Внимательный
читатель сразу вспомнит про WSDL -файлы в технологии SOAP . Сканер может
забирать такой манифест и фаззить методы из него.
web20Spider
Мы пришли к тому, что, если сканеру придется иметь дело не только с "домашними
страницами", но и современными веб-приложениями, то ему не обойтись без
полноценного стека браузерных технологий. Схема работы такого сканера может
выглядеть следующим образом.
В начале сканер проходит авторизацию в тестируемом веб-приложении и получает сессионные
куки. На этапе "кровлинга" он подключает модуль веб-браузера и взаимодействует с
веб-приложением уже с помощью него. При этом модуль браузера шлёт через
прокси-модуль сканера весь трафик веб-приложения. Действия сканера на стороне
браузера (такой "веб2.0 кровлинг" получается) разберём более подробно. В случае
с AJAX-приложением HTTP-запрос к серверной части может быть послан практически при
любом действии (или бездействии) пользователя. У веб-приложения больше нет
страниц в привычном их понимании, но есть состояния. Веб-приложение (клиентская
часть) может переходить из одного состояния в другое по клику на
ссылке (или любом другом объекте) либо при возникновении любого другого события в рамках JavaScript.
Например, пользователь кликнул на ссылку "Настройки" и ему без перезагрузки основной страницы показывается форма с
настройками, которая так же без перезагрузки и отправляется на сервер для
обработки. Таким образом задача в рамках этапа "кровлинга" из "собрать все
ссылки" превращается в "собрать максимально полную карту состояний
веб-приложения". Для упрощения этой, вообще говоря, сложной задачи будем
считать, что URL однозначно идентифицирует состояние веб-приложения. Например, в случае с
экраном "Настройки" URL может выглядеть как http://example.com/app/#settings .
После открытия в модуле браузера "главной страницы" веб-приложения мы можем попросить его,
найти и прокликать все объекты, которые могут быть источниками смены состояния,
например те же ссылки (тег a ) или картинки (тег img ). Запомнить все новые
состояния, которые получились после клика для следующей итерации. Затем проходим
подобным образом по всем собранным состояниям (количество итераций, оно же
глубина обхода, конечно регулируется). Напоминаю, что весь трафик при этом
проходит через сканер и сохраняется для дальнейшего фаззинга.
Конечно можно в качестве модуля браузера использовать в "сыром" виде
соответствующий движок — Gecko, WebKit...
Но эта задача будет равна по сути написанию своего ещё одного браузера,
коих на том же WebKit сейчас великое множество. Но есть вариант интереснее и
проще — а, что если можно использовать полноценный веб-браузер "без иксов", да
ещё и с возможностью скриптинга на JavaScript?! Вот например, PhantomJS ,
который уже пользуется популярностью у разработчиков и тестировщиков.
Это полный стек браузерных технологий на базе WebKit, которым можно управлять
через скриптинг на JavaScript и даже CoffeeScript. Также важным фактором
является то, что, как и движок, это свободный (New BSD License) проект.
Например, в следующем участке кода мы открываем необходимую страницу и можем
выполнять с ней практически всё, что угодно, в контексте её домена.
console.log('Loading a web page');
var page = require('webpage').create();
var url = 'http://www.phantomjs.org/';
page.open(url, function (status) {
//Page is loaded!
phantom.exit();
});
А вот так можно сделать простую, но тем не менее "натуральную", проверку DOM-based XSS:
var page = new WebPage();
var url = "http://example.com/foo.php";
var token = 'xss';
var payload = '?"><script>document.title=String.fromCharCode('+str2ascii(token)+')</script>';
url = url + payload;
page.open(url, function (status) {
if (status !== 'success') {
console.log('Unable to load the address!');
phantom.exit();
}
var title = page.evaluate(function () {
return document.title;
});
if (title == token) {
console.log('DOM-based XSS is found in\nURL: ' + url);
}
phantom.exit();
});
Всё очень просто. В нагрузке пытаемся изменить какой-нибудь DOM-объект и потом
проверяем изменения. В данном случае меняем document.title .
Но использовать в чистом виде PhantomJS может оказаться для специфичных задач
всё же не так удобно, как хотелось бы. В таком случае рекомендую обратить
внимание на другой проект — CasperJS , который по сути является надстройкой над
PhantomJS и даёт, как любят говорить программисты, больше "синтаксического сахара"
и удобства скриптинга на всё том же JavaScript!
Описанный выше алгоритм по обходу состояний веб-приложения, реализованный для PhantomJS/CasperJS,
будет иметь следующий вид:
//...
process_page = function() {
var url;
if (to_visit.length > 0) {
url = to_visit.pop();
states.push(url)
} else {
return;
}
this.thenOpen(url, function() {
var links = this.evaluate(getClickable);
for (var i=0; i<links.length; i++) {
if (this.exists(links[i].path)) {
if (! need_follow_url(links[i].href)) {
continue;
}
this.thenClick(links[i].path).then(function(){
this.wait(300, function() {
var tmp_url = this.getCurrentUrl();
if (url !== tmp_url) {
if (get_domain(tmp_url) == target_domain) {
if (states.indexOf(tmp_url) == -1) {
to_visit.push(tmp_url)
}
}
this.back();
}
});
});
}
}
});
};
for (var i=0; i<=max_deep; i++) {
casper.then(process_page);
}
//...
Немного комментариев по коду. Массив to_visit содержит URL-адреса, которые
необходимо будет учесть при следующей итерации. А states , соответственно, адреса уже обработанных состояний.
Количество итераций есть глубина анализа веб-приложения и задаётся переменной max_deep .
Функция need_follow_url необходима для того, что бы наш паук не уходил
за пределы домена тестируемого веб-приложения. Напоминаю, что мы рассматриваем
всё-таки скорее PoC, чем рабочий вариант, и поэтому считаем, что HTTP-запросы
могут генерировать элементы a и img . Для абсолютной адресации этих
элементов я использовал CSS-селекторы , хотя и кому-то будут больше по душе XPATH.
Ну и наконец мы подошли к самому сканеру и прокси-модулю. В рамках данного
исследования в качестве сканера был выбран w3af .
Для тех, кто не знает, это достаточно мощный "опенсорс" модульный сканер. Эдакий
Метасплоит для веба. Написан он на Питоне, а буква "w" в названии
расшифровывается как фреймфорк (Web Application Attack and Audit Framework), что
позволяет достаточно легко писать для w3af свои модули. В нашем случае был
написан PoC discovery-модуль web20Spider , который как раз и запускал при сканировании
PhantomJS со специальной JS-нагрузкой. Трафик при этом проксируется через другой модуль
w3af и в дальнейшем на этапе аудита используется для всестороннего фаззинга!
В итоге получилось достаточно интересное решение, которое позволяет уже не с таким
ужасом в глазах смотреть на тестирование, в том числе и автоматизированное, современных веб-приложений.
А с учётом того, что у нас есть полноценные браузер и возможность запуска
произвольного JS-кода в контексте тестируемого домена, то мы и вполне можем
поискать клиентские уязвимости!
Заключение
Современные веб-приложения принесли не только скорость работы интерфейса
и прочие удобства для конечно пользователя и веб-разработчкиов, но и порядочно
головной боли для разработчиков сканеров уязвимостей. Уметь полноценно
тестировать такие приложения доступно далеко не всякому сканеру и зачастую это
дорогие коммерческие разработки. Более того, это ещё и хорошая тема для
академического исследования! В этой статье я попытался показать, каким образом
эти проблемы можно решать, а за теми сканерами, которые сумеют это сделать,
будущее. Удел же остальных будет сканировать старые_ламповые веб-странички :)
There are comments .