Продолжим проходжение заданий с площадки Hack The Box. На этот раз задание BountyHunter — взлом сайта на PHP с помощью уязвимости XXE.
Взлом сайта на PHP с помощью уязвимости XXE
Для начала добавим IP-адрес машины в /etc/hosts:
1 |
10.10.10.247 explore.htb |
Сканирование портов
Начнем со сканирования портов. Это стандартная операция при любом пентесте. Сканирование портов позволит определить, какие службы на машине принимают соединение.
Самый популярный сканер — это Nmap. Следующий скрипт улучшит результаты сканирования:
1 2 3 |
#!/bin/bash ports=$(nmap -p- --min-rate=500 $1 | grep ^[0-9] | cut -d '/' -f 1 | tr '\n' ',' | sed s/,$//) nmap -p$ports -A $1 |
Находим два открытых порта:
- 80 (веб‑сервер Apache 2.4.41)
- 22 (служба SSH)
Справка: брутфорс учеток
В нашем распоряжении нет учетных данных, соответственно нет смысла анализировать службы (такие как SSH), которые требуют авторизацию. Вместо этого, можно перебрать пароли брутом, но в прохождении HTB всегда есть другие сценарии. В реальности таких сценариев может вовсе не быть, к тому же есть шансы сбрутить пароль или добыть его его с помощью социальной инженерии.
Хорошенько изучим сайт и соберем важную информацию. Сайт выглядит непримечательно. Есть несколько ссылок и возможность скачать какой‑то файл.
Кнопка нерабочая, но ссылка Portal открывает страницу portal.php с текстом «Portal under development. Go here to test the bounty tracker» и еще какой-то ссылкой. При переходе по этой ссылке появляется форма ввода «Bounty Report System — Beta».
Точка входа
После заполнения данных, мы видим сообщение, что если база данных готова, то наши данные будут добавлены в нее.
Первым делом можно протестировать SQL-инъекцию в запросе Insert, но все же стоит взглянуть на весь путь запросов через Burp. Все запросы можете увидеть в Burp History.
Бросилось в глаза то, что я заполнил четыре параметра, а отправился один, причем в кодировке Base64. Хорошо, что в Burp Pro добавили плагин Inspector, который автоматом разбирает и снимает известные кодировки.
Именно Inspector подсказал мене перейти с тестирования SQL-инъекции на поиск XXE. А все потому, что параметры отправляются в формате XML.
Точка опоры
XXE-инъекция
XML eXternal Entity (XXE) инъекция — это уязвимость в сервисах, которые поддерживают протоколы SOAP и XML-RPC и принимают входные данные в виде XML-документа. Стандарт XML поддерживает включение секции DTD, а секции эти, в свою очередь, могут подключать к документу дополнительные компоненты, так называемые внешние сущности (см. Эксплуатация уязвимости XXE).
Внешние сущности — это отдельные файлы, и добавляются они с помощью ключевого слова SYSTEM и URI. Если XML-парсер невалидирующий, то он может просто загрузить внешнюю сущность и подключить к содержимому XML-документа. У злоумышленника есть возможность подставить в URI внешней сущности file URI, указывающий на удаленный или локальный файл в системе.
А теперь давайте перейдем к нашему примеру. Ниже представлен документ XML, который отправляется на сервер.
1 2 3 4 5 6 7 |
<?xml version="1.0" encoding="ISO-8859-1"?> <bugreport> <title>test</title> <cwe>test</cwe> <cvss>test</cvss> <reward>test</reward> </bugreport> |
Чтобы поискать уязвимость, подключим сначала внутреннюю сущность. Для этого определим секцию DOCTYPE и сущность (ENTITY) testXXE со значением RALF. В самом документе можем обращаться к нашей сущности testXXX как будто через ссылку &testXXE;. Причем вставим последовательность для теста абсолютно во все параметры, чтобы увеличить шансы.
1 2 3 4 5 6 7 8 |
<?xml version="1.0" encoding="ISO-8859-1"?> <!DOCTYPE replace [<!ENTITY testXXE "RALF"> ]> <bugreport> <title>&testXXE;</title> <cwe>&testXXE;</cwe> <cvss>&testXXE;</cvss> <reward>&testXXE;</reward> </bugreport> |
В итоге вместо &testXXE; подставляется значение RALF, что свидетельствует о наличии XXE. Давайте попробуем ее развить: к примеру, прочитать локальный файл в системе. Будем читать доступный всегда и для всех пользователей файл /etc/passwd. Для этого определим сущность, но уже не как локальную строку, а как внешний файл в системе:
1 |
[<!ENTITY testXXE SYSTEM "file:///etc/passwd"> ] |
Документ получился следующий:
1 2 3 4 5 6 7 8 |
<?xml version="1.0" encoding="ISO-8859-1"?> <!DOCTYPE replace [<!ENTITY testXXE SYSTEM "file:///etc/passwd"> ]> <bugreport> <title>&testXXE;</title> <cwe>test</cwe> <cvss>test</cvss> <reward>test</reward> </bugreport> |
Система вернула нам содержимое желаемого файла. В основном все пользователи стандартные и относятся к системе, а вот логин development — нет. Интересно!
Следующий шаг — поиск учетных данных. Раз мы можем читать произвольные файлы, проверим исходники сайта. Начнем с известной нам страницы tracker_diRbPr00f314.php. Но просто передать файл в сущность теперь не выйдет, так как при запросе содержимого кода на PHP веб‑сервер не отобразит, а выполнит его. Чтобы этого не случалось, будем использовать обертку PHP.
PHP wrappers
PHP поставляется со множеством встроенных оберток для обращения к функциям файловой системы, таким как fopen(), copy(), file_exists() и filesize(). В случаях, подобных нашему, чаще всего используется обертка, реализующая схему php://, которая предоставляет доступ к потокам ввода‑вывода.
В частности, как файлы в PHP можно открыть:
- собственные потоки ввода‑вывода PHP;
- дескрипторы стандартного ввода, вывода и потока ошибок;
- временные файловые потоки в памяти и на диске;
- фильтры, которые могут манипулировать другими файловыми ресурсами по мере их считывания или записи.
Разберемся с фильтрами. По адресу php://filter доступна метаобертка, которая позволяет применять фильтры к потоку во время открытия. Это полезно для функций вроде readfile(), file() и file_get_contents(), где нет возможности применить фильтр к потоку до того, как содержимое будет прочитано. А это наш случай.
Поток php://filter принимает как часть пути следующие параметры:
- resource — указывает потоку, что его необходимо отфильтровать;
- read — одно имя или несколько имен фильтров для чтения, разделенных символом |;
- write — одно имя или несколько имен фильтров для записи, разделенных символом |;
- [список фильтров для применения к обеим цепочкам] — список фильтров, который будет применен к обоим потокам на чтение и на запись.
Переходим к практике. Мы можем собрать фильтр‑обертку, который будет кодировать поток ввода еще при чтении кода из файла.
1 |
php://filter/read=convert.base64-encode/resource=tracker_diRbPr00f314.php |
Это приведет к тому, что на сайте будет отображен код требуемой страницы, но сразу закодированный в Base64. Применим обертку в нашей XXE-нагрузке:
1 2 3 4 5 6 7 8 9 |
<?xml version="1.0" encoding="ISO-8859-1"?> <!DOCTYPE replace [<!ENTITY testXXE SYSTEM "php://filter/read=convert.base64-encode/resource=tracker_diRbPr00f314.php"> ]> <bugreport> <title>&testXXE;</title> <cwe>test</cwe> <cvss>test</cvss> <reward>test</reward> </bugreport> |
Теперь в Burp выделяем текст, закодированный в Base64, и нажимаем Ctrl-Shift-B. Откроется новое окно Burp с декодированным текстом.
Ничего интересного в этом файле не находим, как и в коде остальных известных нам страниц сайта. В таком случае просканируем файлы PHP в корневом каталоге сайта, к примеру с помощью ffuf.
Сканирование веба c fuff
Одно из первых действий при тестировании безопасности веб‑приложения — это сканирование методом перебора каталогов, чтобы найти скрытую информацию и недоступные обычным посетителям функции. Для этого можно использовать программы вроде dirsearch и DIRB.
Я предпочитаю легкий и очень быстрый ffuf. При запуске указываем следующие параметры:
- -w — словарь (используем directory-list-2.3-medium из набора SecLists);
- -t — количество потоков;
- -u — URL;
- -fc — исключить из результата ответы с кодом 403.
Список популярных файлов PHP можно найти в наборе словарей SecLists.
1 |
ffuf -u http://bountyhunter.htb/FUZZ -w php_files_common.txt -t 200 -fc 403 |
Там мы находим новый файл — db.php. Это файл, который обеспечивает подключение к базе данных и должен содержать необходимые параметры, в том числе учетные данные для доступа к БД.
Найденный пароль подходит для обнаруженного в файле /etc/passwd юзера development, и мы получаем флаг пользователя.
Локальное повышение привилегий
Для повышения привилегий в Linux всегда стоит проверять настройки sudoers и приложения с выставленным битом SUID.
Справка: sudoers
Файл /etc/sudoers в Linux содержит списки команд, которые разные группы пользователей могут выполнять от имени администратора системы. Можно просмотреть его как напрямую, так и при помощи команды sudo -l.
При проверке sudoers (sudo -l) сразу находим зацепку.
Таким образом, мы можем без ввода пароля выполнить скрипт /opt/skytrain_inc/ticketValidator.py в привилегированном контексте. Посмотрим, что в нем!
Начнем анализ с функции main. Первым делом у нас запросят путь к какому‑то файлу и передадут то, что мы ввели, в функцию load_file. В ней проверяется расширение файла. Если это файл в формате Markdown (.md), то он открывается, а программа продолжает работать.
Затем дескриптор открытого файла передается в функцию evaluate. Она построчно считывает и проверяет файл. Так, первой строкой должно быть выражение # Skytrain Inc, вторая строка должна начинаться с последовательности ## Ticket to, а третья — __Ticket Code:__. Все остальные строки должны начинаться символами **.
Если все проверки пройдены, то, начиная с четвертой строки, в переменную ticketCode будет считываться число между символами ** и +. При этом остаток от деления этого числа на 7 должен быть равен 4. В этом случае вся строка без символов ** передается в функцию eval.
Мы добрались до вызова опасной функции, которая должна выполнять код Python, — тут и кроется уязвимость. Так как Python — это не сценарный язык вроде bash, информацию об ошибках мы получим только во время выполнения кода.
К примеру, если я передам в функцию eval строку 4+sdffds, то программа сразу сигнализирует об ошибке. Но если в ту же функцию eval передать строку (4+print('qwe')), то ошибка будет вызвана не сразу, так как Python сначала выполнит функцию print, а потом eval.
Таким образом, мы можем поместить в итоговый файл код на Python после знака +, и этот код будет выполнен. Можем переходить к выбору нагрузки.
Абсолютно все способы получения управления требуют подключить другие модули. Так как мы не можем использовать последовательность команд через точку с запятой, остается выполнять однострочники по следующему шаблону:
1 |
__import__('module').method('param') |
где module — импортируемый модуль, method — функция из этого модуля, а param — параметры функции.
Подключать будем модуль pty, из которого используем функцию spawn для создания командной оболочки в привилегированном контексте. Объединив всё, получим следующий файл Markdown:
1 2 3 4 |
# Skytrain Inc ## Ticket to ralf __Ticket Code:__ **4+(__import__('pty').spawn('/bin/bash')) |
Машина захвачена, и мы получаем флаг рута.
Заключение
На этом все. Теперь вы знаете, как взломать сайт на PHP с помощью уязвимости XXE.
Еще по теме: