Kubernetes · Report #1249583
Authenticated kubernetes principal with restricted permissions can retrieve ingress-nginx serviceaccount token and secrets across all namespaces
How it was found
Summary:
Retrieving ingress-nginx serviceaccount token
ingress-nginx allows adding custom snippets of nginx configuration to Kubernetes ingress objects. These snippets can be applied to either the relevant location {} or server {} blocks with the following annotations, respectively.
- https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/annotations/#configuration-snippet
- https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/annotations/#server-snippet
Inside the server {} block we can add a custom snippet of lua-code that reads the serviceaccount token that is mounted inside the ingress-nginx pod. We then set it as an nginx variable and return it to the client at a configured location. This might look like this:
nginx.ingress.kubernetes.io/server-snippet: |
set_by_lua $token '
local file = io.open("/run/secrets/kubernetes.io/serviceaccount/token")
if not file then return nil end
local content = file:read "*a"
file:close()
return content
';
location = /token {
content_by_lua_block {
ngx.say(ngx.var.token)
}
}
Impact
The ingress-nginx serviceaccount has the permissions to list secrets across all namespaces. With the ingress-nginx serviceaccount's token a user, with otherwise restricted privileges, can at least:
- exfiltrate all kubernetes secrets
- get tokens of all kubernetes serviceaccounts; allowing an attacker to elevate his privileges to potentially cluster-admin
Vendors such as rancher-labs bundle ingress-nginx, or a forked version of ingress-nginx, with their software. Solutions provided by these vendors might also be vulnerable.
kube-apiserver proxy
ingress-nginx can be configured to expose the Kubernetes kube-apiserver by creating a Kubernetes Service of type ExternalName and pointing it to kubernetes.default; the hostname at which the kubernetes api is available inside the cluster. This can expose an otherwise private and protected kube-apiserver to untrusted networks like the internet.
Requirements to exploit
To successfully exploit this vulnerability an attacker would need access to an already authenticated user or serviceaccount that has the permissions to create the following resources inside kubernetes:
ingressservice
Additionally the attacker needs network access to the ingress-nginx-controller loadbalancer or in-cluster service to retrieve the ingress-nginx serviceaccount token. The hostname configured in the ingress object does not necessarily have to resolve to the ingress-nginx-controller's loadbalancer; ingress-nginx will also serve us the token if we manually add the Host-header.
Kubernetes Version:
Any, as far as I am aware. This was tested with AWS EKS 1.20.
Component Version:
Any, as far as I am aware. This was tested with the following release of ingress-nginx:
- chart:
ingress-nginx-3.33.0 - application:
0.47.0
Steps To Reproduce:
I created a proof-of-concept (poc.sh) that requires the following:
- A kubernetes cluster with ingress-nginx installed; ingress-nginx should not be restricted to a single namespace
- A local kubeconfig file configured to communicate with the kubernetes cluster
- A user configured in the kubeconfig file with the permissions to
createingressandserviceobjects in the namespace configured in the kubeconfig context
The proof-of-concept requires setting the INGRESS_HOST environment variable. This variable should contain a hostname that resolves to the ingress-nginx-controller's loadbalancer. This is made easy on clusters where a wildcard DNS-record is pointing to the loadbalancer.
When invoked, the script will:
- Apply the required
ingressandservice;- exposing the ingress-nginx serviceaccount token at
https://$INGRESS_HOST/token - proxying all requests to the kubernetes apiserver at
https://$INGRESS_HOST
- exposing the ingress-nginx serviceaccount token at
- Retrieve the ingress-nginx serviceaccount token
- Write a local kubeconfig;
- Using the kube-apiserver proxy
- Using the ingress-nginx serviceaccount token
- Write
secretsfrom all namespaces to a local file calledsecrets.json - For each serviceaccount token found in
secrets.jsoncheck if the serviceaccount has cluster-admin privileges. If so, create a new user and context in the local kubeconfig file with the serviceaccount's token
Supporting Material/References:
| file | description |
|---|---|
ingress.yaml | kubernetes manifest used to create required service and ingress objects |
poc.sh | proof-of-concept written in bash |
output.png | output of running poc.sh against local test cluster<br>getting cluster-admin by finding the serviceaccount tokens of flux and flux-helm-operator |
Impact
- exfiltrate all kubernetes secrets
- get tokens of all kubernetes serviceaccounts; allowing an attacker to elevate his privileges to potentially cluster-admin