Recently several new CVEs in the ingress nginx controller for Kubernetes were announced. I thought I’d take a closer look at one of them, CVE-2023-5044. Whilst there’s some details in the CVE announcement and some hints in a post from the CVE reporter here there’s not any actual PoC that I could find, so I decided to see if I could write one!

Test environment setup

As is often the case, the easiest way to set up a test environment was to use KinD. They have a page with instructions for ingress setups here which works well.

Once we have ingress setup with the sample applications provided curl’ing http://localhost/foo and http://localhost/bar will hit paths managed by the ingress controller.

Experimenting with the vulnerability

So we know from the advisory that the problem lies in the nginx.ingress.kubernetes.io/permanent-redirect annotation, so one of the first things I thought to try was classic command injection where you end the statement that’s being provided and start a new directive. In the case of nginx this uses the ; character, so I tried that and it seemed to work!

Exec’ing into the ingress controller pod, I could see that what happens with this annotation is that anything you provide is basically injected directly into the nginx config file used by the controller.

One option I experimented with, as it seems like a good way to get access to sensitive files (like the service account token for the controller which has high privileges to the cluster) would be to use the alias or root directives to serve up the file. However these directives have been disabled so that wasn’t going to work.

Fortunately I remembered that lua scripting is sometimes supported in nginx, so we might be able to use that. With a bit of help from ChatGPT on the exact syntax of what to use, I was able to get a working PoC.

nginx.ingress.kubernetes.io/permanent-redirect: https://www.mccune.org.uk;}location ~* "^/flibble(/|$)(.*)" {content_by_lua 'ngx.say(io.popen("cat /var/run/secrets/kubernetes.io/serviceaccount/token"):read("*a"))';}location ~* "^/flibblea(/|$)(.*)" { content_by_lua 'os.execute("touch /you")'

This could definitely be neater, but what it does is close off the location directive with ;} after the URL that we’re redirecting to. Then open a new location on the path /flibble. When someone calls that path we run a lua script that uses io.popen to run an OS command and then returns that to the caller using nginx.say. After that I just put another location directive to absorb any unwanted directives that were already in the file (it’s important that you balance up the braces in the file, otherwise your change will get rejected).

With that in place you can curl localhost/flibble and get back the service account token for the ingress which has high privileges to the cluster, notably GET secrets at the cluster level.

Conclusion

This is an interesting vulnerability and one that (despite being a bit fiddly) wasn’t too difficult to exploit. In terms of risk however, it’s quite situational as it requires rights to edit ingress objects in a namespace, so it’s not something an attacker outside the cluster is likely to be able to execute.


raesene

Security Geek, Kubernetes, Docker, Ruby, Hillwalking