Сегодня вновь очень активно развивается тема автоматизации тестирования безопасности веб-приложений с использованием PhantomJS в связке с BurpSuite, ModSecurity, Garmr и т.д. Я не стал исключением, о своём опыте разработки относительно рабочего прототипа сканера с поддержкой Javascript, Ajax и DomMutation я бы и хотел с вами поделится. Может это поможет кому-то разработать собственное решение, которое будет гораздо лучше. Всех заинтересованных прошу под кат:-)
Надеюсь, никто не станет спорить, что существующие веб-приложения это адское варево из различных технологий и чем дальше тем больше логики перемещается с серверной части на клиентскую. Разумеется, это не могло не сказаться на отделах ИБ, которые и без того зачастую не столь велики, а тут еще и привычные методики автоматизированного тестирования просто обламываются на корню. В один момент стало ясно, что для первичного анализа решений уже не хватает статического анализа и привычной схемы html-парсер+фаззер. Так было принято решение исследовать этот вопрос и попробовать что-то с этим сделать.
Почему поддержка Javascript, Ajax и мутаций столь важна?
Потому как иначе никак:-) История знает массу примеров, когда отсутствие даже простейшей поддержки Javascript в сканерах безопасности сводила их эффективность на нет. К примеру, CSRF в Яндекс.Почта — если в GET параметрах передать изменение настроек пользователя, бекенд честно ответит ошибкой о невалидном CSRF-токене и тогда JavaScript движок переотправлял(!) этот же запрос, но уже дополнив запрос валидным токеном:) Получается, с позиции бекенда все более чем правильно, но с позиции клиента… Или XSS при переводе письма на любой из языков — история аналогичная, примитивная уязвимость которую не смог бы обнаружить w3af и иже с ним. Отсутствие поддержки JavaScript на текущий момент времени, кажется просто недопустимым и необходимо всеми силами стараться это исправить.
Как быть?
Я решил воспользоваться существующими идеями и взять за основу любимые SlimerJS и CasperJS и попробовать получить на выходе crawler с возможностью рекурсивного обхода событий элементов DOM + мониторинг мутаций для выявления «патологий», XSS уязвимостей и прочего. Почему не PhantomJS? Потому как в нем пока нет поддержки MutationObserver, который был мне нужен для анализа мутаций. И так, я представил себе целостную систему состоящую из четырёх больших блоков:
* Кровлер, который как обезьянка обходит все нужные нам события и отслеживает мутации на основе формальных правил
* Прокси, который смог бы собрать данные для дальнейшего фаззинга и запуска остальной части проверок зависящих от контекста
* Фикстуры в веб-приложении, с заранее подготовленными метками, на которые сможем ориентироваться в процессе тестирования.
* Отчет на основе diff'а с предыдущими результатами сканирования
Большая часть из них у меня было реализована ранее и поэтому я сконцентрировался на первой части (если будет интерес, я расскажу и о остальных). Общий алгоритм работы, который видится мне, разбит на два больших участка — обработка страниц и обработка событий, в упрощенном виде выглядит примерно так:
Как видите все достаточно просто, хоть пока и не идеально, для простоты восприятия я рисовал только основные блоки. Представим себе веб-приложение которое выводит пользователей и по клику отображает дополнительную инфу полученную Ajax-запросом:
Доступно тут (пока не убьёт Хабраеффект): http://crawl-test.mmmkay.info/.
Исходный ход: https://github.com/dharrya/monkey-crawler/tree/master/tests
К примеру, если запустить w3af, с плагином webSpider, то никаких Ajax запросов обнаружено не будет, а между тем они не редко уязвимы по ряду очевидных причин.У Skipfish будет примерно такой же результат.
Я подготовил небольшой прототип для подтверждения работоспособности алгоритма, доступен на github. В корне проекта есть тестовый скрипт «test.js»:
(function start(require) {
"use strict";
var Spider = require('lib/spider').Spider;
var utils = require('utils');
var startTime = new Date().getTime();
var spider = new Spider();
var url = 'https://github.com/dharrya';
if (spider.cli.has(0))
url = spider.cli.get(0);
spider.initialize({
targetUri: url,
eventContainer: undefined
});
spider.start(url);
spider.then(spider.process);
spider.run(function() {
this.echo('n<---------- COMPLETED ---------->n');
var deltaTime = new Date().getTime() - startTime;
deltaTime = (deltaTime / 1000).toFixed(2);
this.echo('time: ' + deltaTime + 'sec');
this.echo('Processed pages:' + this.pagesQueue.length);
utils.dump(this.pages);
spider.exit();
});
})(require);
Суть которого запустить процесс и вывести «сырой» результат, опробуем на моём примере:
$ ./test.sh http://crawl-test.mmmkay.info
<---------- COMPLETED ---------->
time: 3.61sec
Processed pages:1
[
{
"url": "http://crawl-test.mmmkay.info/",
"opened": true,
"processed": true,
"reloadCount": 0,
"status_code": 200,
"jsErrors": [],
"xss": [],
"xssHashMap": [],
"pages": [],
"events": [
{
"eventType": "click",
"path": "id("user-Lisa")/DIV[3]/BUTTON[1]",
"parentEvent": null,
"depth": 0,
"status": "completed",
"completed": true,
"deleted": false,
"xss": [],
"xssHashMap": [],
"events": [
{
"eventType": "click",
"path": "//DIV[4]",
"parentEvent": null,
"depth": 1,
"status": "completed",
"completed": true,
"deleted": false,
"xss": [],
"xssHashMap": [],
"events": [],
"resourses": []
}
],
"resourses": [
"http://crawl-test.mmmkay.info/user/Lisa.json"
]
},
{
"eventType": "click",
"path": "id("user-Jimmy")/DIV[3]/BUTTON[1]",
"parentEvent": null,
"depth": 0,
"status": "completed",
"completed": true,
"deleted": false,
"xss": [
{
"innerHtml": "Another XSS...",
"path": "id("userInfoDescription")/XSSMARK[1]",
"initiator": null,
"dbRecord": null
}
],
"xssHashMap": [
0
],
"events": [],
"resourses": [
"http://crawl-test.mmmkay.info/user/Jimmy.json"
]
},
{
"eventType": "click",
"path": "id("user-Mark")/DIV[3]/BUTTON[1]",
"parentEvent": null,
"depth": 0,
"status": "completed",
"completed": true,
"deleted": false,
"xss": [],
"xssHashMap": [],
"events": [],
"resourses": [
"http://crawl-test.mmmkay.info/user/Mark.json"
]
},
{
"eventType": "click",
"path": "id("user-Tommy")/DIV[3]/BUTTON[1]",
"parentEvent": null,
"depth": 0,
"status": "completed",
"completed": true,
"deleted": false,
"xss": [],
"xssHashMap": [],
"events": [],
"resourses": [
"http://crawl-test.mmmkay.info/user/Tommy.json"
]
}
],
"deferredEvents": [],
"startTime": 1391266464847,
"endTime": 1391266466787,
"resourses": [
"http://crawl-test.mmmkay.info/"
]
}
]
Видите у событий Ajax запросы за данными о пользователе? Это как раз то, что нам нужно! Согласен, текущий отладочный вывод немного избыточен, зато информативен. Из него видно, что он успешно обнаружил дополнительные запросы при обработке событий кнопок «More info», которые можно пофаззить в будущем. В довесок благодаря фикстурам в веб-приложении он смог сразу же обнаружить и XSS в мутациях, о чем любезно сообщил. Пока работает не очень быстро, но я активно работаю над этим в свободное время. Другой отличный пример это linkedin, вот результат работы для 5ти его страниц (начиная с главной):
Зеленные узлы — обработанные события элементов
Синие — ресурсы которые были запрошены при их обработке
Как видите, в подобных веб-приложениях с множеством «цепочных» событий этот подход может быть эффективен!
Итог
Я думаю в будущем развить эту идею до полноценного сканера безопасности веб-приложений и дополнив остальной обвязкой выложить (если руководство разрешит), может в виде плагина для W3af или Minion. Но перед этим остается еще множество не решенных вопросов связанных с производительностью и критически важными фичами.
Надеюсь не очень вас утомил и мои попытки будут кому-то полезны, если у меня не вышло что-то внятно донести — говорите, я постараюсь сорвать покров.
Автор: dharrya