Add local cloud Kubernetes updates

This commit is contained in:
Carlos Polop
2026-07-04 00:45:00 +02:00
parent 42c3a88de7
commit fdd3d95668
10 changed files with 358 additions and 56 deletions
@@ -74,6 +74,24 @@ You can read more about `gcloud` for containers [here](https://cloud.google.com/
This is a simple script to enumerate kubernetes in GCP: [https://gitlab.com/gitlab-com/gl-security/security-operations/gl-redteam/gcp_k8s_enum](https://gitlab.com/gitlab-com/gl-security/security-operations/gl-redteam/gcp_k8s_enum)
### Current GKE identity and metadata checks
When reviewing modern GKE clusters, separate Google Cloud IAM permissions, Kubernetes RBAC, pod workload identity, and node credentials. A Google principal can often retrieve cluster endpoint data with `container.clusters.get`, but the resulting Kubernetes requests still need to pass GKE/Kubernetes authorization and any network restrictions such as private endpoints or authorized networks.
Workload Identity Federation for GKE is the preferred way for pods to access Google Cloud APIs. Check whether the cluster has a workload pool and whether Kubernetes service accounts are mapped directly as IAM principals or are allowed to impersonate IAM service accounts:
```bash
gcloud container clusters describe <cluster> --region <region> \
--format='value(workloadIdentityConfig.workloadPool)'
kubectl get serviceaccounts -A -o yaml | grep -n 'iam.gke.io' -B 5 -A 8
kubectl get pods -A -o custom-columns='NS:.metadata.namespace,NAME:.metadata.name,SA:.spec.serviceAccountName,NODE:.spec.nodeName'
```
If a service account has the annotation `iam.gke.io/gcp-service-account`, review the IAM service account policy for `roles/iam.workloadIdentityUser` grants to Kubernetes service account principals. Also check IAM allow policies for direct workload identity principals or broad principal sets.
Metadata access depends on cluster mode, node pool configuration, and workload settings. Do not assume every pod can steal the node service account. In Workload Identity-enabled environments, ordinary pods should use the GKE metadata server to obtain the workload identity intended for their Kubernetes service account. Node compromise, `hostNetwork` pods in some Standard configurations, and legacy node metadata exposure can still change the blast radius, so verify the actual node pool metadata mode, node service account, OAuth scopes, and pod placement.
### TLS Boostrap Privilege Escalation
Initially this privilege escalation technique allowed to **privesc inside the GKE cluster** effectively allowing an attacker to **fully compromise it**.
@@ -104,4 +122,3 @@ Even if the API **doesn't allow to modify resources**, it could be possible to f
{{#include ../../../banners/hacktricks-training.md}}
@@ -357,7 +357,8 @@ curl -v -H "Authorization: Bearer <jwt_token>" https://<master_ip>:<port>/api/v1
### Creating and Reading Secrets
There is a special kind of a Kubernetes secret of type **kubernetes.io/service-account-token** which stores serviceaccount tokens.
There is a special kind of Kubernetes Secret of type **kubernetes.io/service-account-token** which stores service account tokens. Modern Kubernetes versions do **not** automatically create one long-lived Secret for every ServiceAccount; projected, bound TokenRequest tokens are the normal workload path. However, manually created service account token Secrets are still supported, and upgraded or legacy clusters may still contain long-lived token Secrets. Current clusters can also mark unused auto-generated legacy token Secrets invalid and eventually clean them up, leaving labels such as `kubernetes.io/legacy-token-invalid-since` and `kubernetes.io/legacy-token-last-used`.
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
@@ -870,4 +871,3 @@ https://github.com/aquasecurity/kube-bench
{{#include ../../../banners/hacktricks-training.md}}
@@ -116,6 +116,8 @@ kubectl get services --all-namespaces -o=custom-columns='NAMESPACE:.metadata.nam
Traffic that ingresses into the cluster with the **external IP** (as **destination IP**), on the Service port, will be **routed to one of the Service endpoints**. `externalIPs` are not managed by Kubernetes and are the responsibility of the cluster administrator.
`externalIPs` is a sensitive route-control field because a user who can set it might claim traffic for an IP address the Service owner should not control if the surrounding network routes that IP to the cluster. Kubernetes announced the deprecation and planned removal of Service `externalIPs` in v1.36, so prefer controller-owned exposure mechanisms such as LoadBalancer integrations or Gateway API where possible, and restrict/admit this field carefully while it still exists.
In the Service spec, `externalIPs` can be specified along with any of the `ServiceTypes`. In the example below, "`my-service`" can be accessed by clients on "`80.11.12.10:80`" (`externalIP:port`)
```yaml
@@ -160,6 +162,21 @@ List all ExternalNames:
kubectl get services --all-namespaces | grep ExternalName
```
### EndpointSlices
EndpointSlices show the concrete backend addresses and ports that a Service currently routes to. They are especially useful when a Service has no selector, when labels do not explain the traffic path, or when only some backends are ready.
List EndpointSlices associated with Services:
```bash
kubectl get endpointslices --all-namespaces
kubectl get endpointslice -n <namespace> -l kubernetes.io/service-name=<service-name> -o yaml
kubectl get endpointslice -n <namespace> -l kubernetes.io/service-name=<service-name> \
-o custom-columns='NAME:.metadata.name,ADDR:.endpoints[*].addresses,READY:.endpoints[*].conditions.ready,PORTS:.ports[*].port'
```
When reviewing exposure, compare the Service selector with the EndpointSlice `targetRef`, endpoint addresses, readiness conditions, and ports. A selectorless Service can be paired with manually managed EndpointSlices and route traffic to non-Pod or unexpected destinations.
### Ingress
Unlike all the above examples, **Ingress is NOT a type of service**. Instead, it sits **in front of multiple services and act as a “smart router”** or entrypoint into your cluster.
@@ -171,28 +188,37 @@ The default GKE ingress controller will spin up a [HTTP(S) Load Balancer](https:
The YAML for a Ingress object on GKE with a [L7 HTTP Load Balancer](https://cloud.google.com/compute/docs/load-balancing/http/) might look like this:
```yaml
apiVersion: extensions/v1beta1
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-ingress
spec:
backend:
serviceName: other
servicePort: 8080
defaultBackend:
service:
name: other
port:
number: 8080
rules:
- host: foo.mydomain.com
http:
paths:
- backend:
serviceName: foo
servicePort: 8080
- path: /
pathType: Prefix
backend:
service:
name: foo
port:
number: 8080
- host: mydomain.com
http:
paths:
- path: /bar/*
- path: /bar
pathType: Prefix
backend:
serviceName: bar
servicePort: 8080
service:
name: bar
port:
number: 8080
```
List all the ingresses:
@@ -207,12 +233,29 @@ Although in this case it's better to get the info of each one by one to read it
kubectl get ingresses --all-namespaces -o=yaml
```
### Gateway API
Gateway API is the newer Kubernetes API for exposing Services. It separates infrastructure-owned Gateway objects from application-owned Route objects such as HTTPRoute. This is useful for delegation, but it also means exposure can be split across namespaces.
List Gateway API exposure objects:
```bash
kubectl get gatewayclasses
kubectl get gateways --all-namespaces
kubectl get httproutes --all-namespaces
kubectl get gateway -n <namespace> <gateway-name> -o yaml
kubectl get httproute -n <namespace> <route-name> -o yaml
```
Check Gateway listeners, allowed route namespaces, Route `parentRefs`, hostnames, filters, backend references, and status conditions such as whether the route was accepted. A Route that is accepted by a shared Gateway can expose a backend even when no legacy Ingress object exists.
### References
- [https://medium.com/google-cloud/kubernetes-nodeport-vs-loadbalancer-vs-ingress-when-should-i-use-what-922f010849e0](https://medium.com/google-cloud/kubernetes-nodeport-vs-loadbalancer-vs-ingress-when-should-i-use-what-922f010849e0)
- [https://kubernetes.io/docs/concepts/services-networking/service/](https://kubernetes.io/docs/concepts/services-networking/service/)
- [https://kubernetes.io/blog/2026/05/14/kubernetes-v1-36-deprecation-and-removal-of-service-externalips/](https://kubernetes.io/blog/2026/05/14/kubernetes-v1-36-deprecation-and-removal-of-service-externalips/)
- [https://kubernetes.io/docs/concepts/services-networking/endpoint-slices/](https://kubernetes.io/docs/concepts/services-networking/endpoint-slices/)
- [https://gateway-api.sigs.k8s.io/](https://gateway-api.sigs.k8s.io/)
{{#include ../../banners/hacktricks-training.md}}
@@ -192,6 +192,23 @@ k api-resources --namespaced=false #Resources NOT specific to a namespace
{{#endtab }}
{{#endtabs }}
### Object metadata worth checking
When you can read an object, export the full YAML or JSON instead of relying only on table output or `describe`. The most useful security context is often in generic object fields that exist across many resource types:
```bash
kubectl get pod <pod> -n <ns> -o yaml
kubectl get deploy <deploy> -n <ns> -o json | jq '.metadata, .spec, .status'
kubectl get pods -A -o custom-columns='NS:.metadata.namespace,NAME:.metadata.name,SA:.spec.serviceAccountName,NODE:.spec.nodeName,PHASE:.status.phase'
```
- `metadata.uid`, `name`, `namespace`, `apiVersion` and `kind` identify the exact object and avoid confusion between objects with the same name in different namespaces or API groups.
- `metadata.labels` and selectors connect Services, Deployments, ReplicaSets, Pods, NetworkPolicies and automation. Following selectors is often the fastest way to identify the real backend pods for a Service.
- `metadata.annotations` can leak operational context such as ingress behavior, cloud load balancer settings, GitOps or Helm metadata, policy exemptions, and service mesh configuration. They should not contain secrets, but real clusters often expose useful clues there.
- `metadata.ownerReferences` shows controller lineage. If a Pod is owned by a ReplicaSet owned by a Deployment, changing or deleting only the Pod usually does not fix the source.
- `metadata.finalizers` and `metadata.deletionTimestamp` explain resources stuck in deletion and can reveal cleanup controllers or persistence/disruption tricks.
- `status`, Events, and conditions can reveal node placement, pod IPs, image IDs, failure messages, scheduling issues, admission denials, and controller progress. They are useful clues, but audit logs are still required to prove who performed an action.
### Get Current Privileges
{{#tabs }}
@@ -330,7 +347,7 @@ kurl -k -v https://$APISERVER/api/v1/namespaces/{namespace}/serviceaccounts
### Get Deployments
The deployments specify the **components** that need to be **run**.
Deployments specify the desired state for stateless application workloads. They create ReplicaSets, and those ReplicaSets create Pods.
{{#tabs }}
{{#tab name="kubectl" }}
@@ -345,7 +362,30 @@ k get deployments -n custnamespace
{{#tab name="API" }}
```bash
kurl -v https://$APISERVER/api/v1/namespaces/<namespace>/deployments/
kurl -v https://$APISERVER/apis/apps/v1/namespaces/<namespace>/deployments/
```
{{#endtab }}
{{#endtabs }}
### Get StatefulSets
StatefulSets manage Pods that need stable names, ordered rollout behavior, and often per-replica persistent volumes.
{{#tabs }}
{{#tab name="kubectl" }}
```bash
k get statefulsets
k get statefulsets -n custnamespace
```
{{#endtab }}
{{#tab name="API" }}
```bash
kurl -v https://$APISERVER/apis/apps/v1/namespaces/<namespace>/statefulsets/
```
{{#endtab }}
@@ -391,7 +431,7 @@ k get services -n custnamespace
{{#tab name="API" }}
```bash
kurl -v https://$APISERVER/api/v1/namespaces/default/services/
kurl -v https://$APISERVER/api/v1/namespaces/<namespace>/services/
```
{{#endtab }}
@@ -421,7 +461,7 @@ kurl -v https://$APISERVER/api/v1/nodes/
### Get DaemonSets
**DaeamonSets** allows to ensure that a **specific pod is running in all the nodes** of the cluster (or in the ones selected). If you delete the DaemonSet the pods managed by it will be also removed.
**DaemonSets** ensure that a **specific Pod is running on all selected nodes** of the cluster. If you delete the DaemonSet, the Pods managed by it will also be removed.
{{#tabs }}
{{#tab name="kubectl" }}
@@ -435,21 +475,22 @@ k get daemonsets
{{#tab name="API" }}
```bash
kurl -v https://$APISERVER/apis/extensions/v1beta1/namespaces/default/daemonsets
kurl -v https://$APISERVER/apis/apps/v1/namespaces/<namespace>/daemonsets
```
{{#endtab }}
{{#endtabs }}
### Get cronjob
### Get Jobs
Cron jobs allows to schedule using crontab like syntax the launch of a pod that will perform some action.
Jobs create Pods that run until completion. They are commonly used for migrations, backups, batch work, and one-off administrative tasks.
{{#tabs }}
{{#tab name="kubectl" }}
```bash
k get cronjobs
k get jobs
k get jobs -n custnamespace
```
{{#endtab }}
@@ -457,7 +498,30 @@ k get cronjobs
{{#tab name="API" }}
```bash
kurl -v https://$APISERVER/apis/batch/v1beta1/namespaces/<namespace>/cronjobs
kurl -v https://$APISERVER/apis/batch/v1/namespaces/<namespace>/jobs
```
{{#endtab }}
{{#endtabs }}
### Get CronJobs
CronJobs use a crontab-like schedule to create Jobs that launch Pods for task-style execution.
{{#tabs }}
{{#tab name="kubectl" }}
```bash
k get cronjobs
k get cronjobs -n custnamespace
```
{{#endtab }}
{{#tab name="API" }}
```bash
kurl -v https://$APISERVER/apis/batch/v1/namespaces/<namespace>/cronjobs
```
{{#endtab }}
@@ -849,6 +913,3 @@ https://www.cyberark.com/resources/threat-research-blog/kubernetes-pentest-metho
{{#endref}}
{{#include ../../banners/hacktricks-training.md}}
@@ -55,12 +55,32 @@ Note that the attributes set in **both SecurityContext and PodSecurityContext**,
| <p><a href="https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.23/#securitycontext-v1-core"><strong>seccompProfile</strong></a><br><a href="https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.23/#seccompprofile-v1-core"><em>SeccompProfile</em></a></p> | The **seccomp options** to use by this container. |
| <p><a href="https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.23/#securitycontext-v1-core"><strong>windowsOptions</strong></a><br><a href="https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.23/#windowssecuritycontextoptions-v1-core"><em>WindowsSecurityContextOptions</em></a></p> | The **Windows specific settings** applied to all containers. |
## Practical workload review checklist
When reviewing a Pod or workload template, inspect both `spec.securityContext` and every container-level `securityContext` under `containers`, `initContainers`, and `ephemeralContainers`. Container-level fields can override the pod-level defaults, so a safe-looking pod default does not guarantee that every container is safe.
High-risk combinations to prioritize:
- `privileged: true`, especially with `hostPID`, `hostIPC`, `hostNetwork`, `hostPath`, host ports, or runtime socket mounts.
- Added capabilities such as `SYS_ADMIN`, `NET_ADMIN`, `SYS_PTRACE`, `SYS_MODULE`, `DAC_READ_SEARCH`, or `DAC_OVERRIDE`.
- `allowPrivilegeEscalation: true` or unset in containers that can execute attacker-controlled code.
- `seccompProfile: Unconfined`, `procMount: Unmasked`, or missing runtime profiles on sensitive workloads.
- Writable root filesystems or broad writable volume mounts in workloads that process untrusted input.
- Missing CPU, memory, or ephemeral-storage requests and limits in multi-tenant namespaces.
For most application workloads, a good baseline is to run as a non-root UID, set `runAsNonRoot: true`, set `allowPrivilegeEscalation: false`, drop all capabilities and add back only the minimum required ones, use `seccompProfile: RuntimeDefault`, prefer a read-only root filesystem, and avoid host namespaces, hostPath mounts, and privileged mode.
At cluster level, use [Pod Security Admission](https://kubernetes.io/docs/concepts/security/pod-security-admission/) namespace labels to enforce the Kubernetes [Pod Security Standards](https://kubernetes.io/docs/concepts/security/pod-security-standards/) where possible. Use `restricted` for namespaces that can support it, at least `baseline` for ordinary application namespaces, and keep privileged exceptions narrow, documented, and isolated to trusted platform namespaces or node pools.
## References
- [https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.23/#podsecuritycontext-v1-core](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.23/#podsecuritycontext-v1-core)
- [https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.23/#securitycontext-v1-core](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.23/#securitycontext-v1-core)
- [https://kubernetes.io/docs/tasks/configure-pod-container/security-context/](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/)
- [https://kubernetes.io/docs/concepts/security/linux-kernel-security-constraints/](https://kubernetes.io/docs/concepts/security/linux-kernel-security-constraints/)
- [https://kubernetes.io/docs/concepts/security/pod-security-standards/](https://kubernetes.io/docs/concepts/security/pod-security-standards/)
- [https://kubernetes.io/docs/concepts/security/pod-security-admission/](https://kubernetes.io/docs/concepts/security/pod-security-admission/)
{{#include ../../../banners/hacktricks-training.md}}
@@ -160,6 +160,9 @@ Therefore, the pod will send the **DNS requests to the address 10.96.0.10** whic
>
> Moreover, if the **DNS server** is in the **same node as the attacker**, the attacker can **intercept all the DNS request** of any pod in the cluster (between the DNS server and the bridge) and modify the responses.
> [!NOTE]
> Validate the active CNI and DNS path before assuming this works in a real cluster. Some CNIs route or isolate same-node traffic differently, and clusters using NodeLocal DNSCache may send pod DNS queries to a node-local address before forwarding to CoreDNS. In those environments, DNS spoofing depends on pod placement, packet capabilities, resolver configuration, node-local cache behavior, and whether applications verify peers with TLS or another identity mechanism.
## ARP Spoofing in pods in the same Node
Our goal is to **steal at least the communication from the ubuntu-victim to the mysql**.
@@ -281,6 +284,8 @@ google.com. 1 IN A 1.1.1.1
A user with write permissions over the configmap `coredns` in the kube-system namespace can modify the DNS responses of the cluster.
Also review NodeLocal DNSCache if it is deployed. It usually runs as a hostNetwork DaemonSet and has its own ConfigMap, logs, cache, and forwarding path. A CoreDNS change may not be the only place where DNS behavior can be affected or observed.
Check more information about this attack in:
{{#ref}}
@@ -330,4 +335,3 @@ It will install agents in the selected pods and gather their traffic information
{{#include ../../banners/hacktricks-training.md}}
@@ -298,6 +298,8 @@ To access the node metadata endpoint you need to:
(Note that the metadata endpoint is at 169.254.169.254 as always).
In newer EKS environments, verify the node and cluster mode before assuming pods can reach the node instance profile. Amazon Linux 2023 EKS optimized AMIs default the IMDS hop limit to 1, and EKS Auto Mode enables `disablePodIMDS` by default, so ordinary pods should not receive node-role credentials unless the operator changed those settings or the pod has another node-level path such as `hostNetwork` or node compromise. The recommended pattern is to block pod access to node IMDS and use IRSA or EKS Pod Identity for workload AWS permissions.
To **escape to the node** you can use the following command to run a pod with `hostNetwork` enabled:
```bash
@@ -336,13 +338,64 @@ kubectl --context=node1 create token -n ns1 sa-priv \
--bound-object-uid=7f7e741a-12f5-4148-91b4-4bc94f75998d
```
## Azure / AKS
In AKS, keep three identity paths separated during assessment:
- **Azure to Kubernetes**: Azure principals can retrieve user or admin kubeconfigs through Azure Resource Manager if their Azure RBAC role allows it. Local admin kubeconfigs from `az aks get-credentials --admin` are certificate-based credentials and can bypass normal Microsoft Entra user/group governance unless local accounts are disabled.
- **Microsoft Entra to Kubernetes**: Entra-integrated clusters authenticate users, groups, or service principals through `kubelogin`/exec kubeconfigs. The final Kubernetes action can be authorized by native Kubernetes RBAC or by Azure RBAC for Kubernetes Authorization.
- **Kubernetes to Azure**: Pods should normally use Microsoft Entra Workload ID, which exchanges projected Kubernetes service account tokens with Entra through the AKS OIDC issuer and federated identity credentials.
Useful AKS identity checks from Azure:
```bash
az aks show -g <resource-group> -n <cluster> \
--query '{disableLocalAccounts:disableLocalAccounts,enableAzureRBAC:enableAzureRBAC,oidcIssuerProfile:oidcIssuerProfile,securityProfile:securityProfile,identity:identity,identityProfile:identityProfile,nodeResourceGroup:nodeResourceGroup}' \
-o yaml
AKS_ID=$(az aks show -g <resource-group> -n <cluster> --query id -o tsv)
az role assignment list --scope "$AKS_ID" --include-inherited -o table
az role assignment list --scope "$AKS_ID/namespaces/<namespace>" -o table
```
From Kubernetes, search for AKS Workload ID signals:
```bash
kubectl get serviceaccounts -A -o yaml | grep -n 'azure.workload.identity' -B 6 -A 8
kubectl get pods -A -o yaml | grep -n 'azure.workload.identity/use' -B 8 -A 8
```
The relevant Workload ID fields are usually:
```yaml
metadata:
annotations:
azure.workload.identity/client-id: "<application-or-managed-identity-client-id>"
azure.workload.identity/tenant-id: "<tenant-id>"
---
metadata:
labels:
azure.workload.identity/use: "true"
```
If the cluster still uses the deprecated Microsoft Entra pod-managed identity model, look for the old CRDs and NMI/MIC components instead of the Workload ID annotations:
```bash
kubectl get crd | grep -i azureidentity
kubectl get azureidentity,azureidentitybinding,azureassignedidentity -A -o yaml 2>/dev/null
kubectl get ds -A | grep -Ei 'nmi|mic|aad-pod-identity'
```
AKS nodes are Azure VM scale set instances, so node or host-level access can expose Azure Instance Metadata Service at `169.254.169.254`. Do not assume an ordinary pod should receive node managed identity credentials: verify workload identity settings, legacy pod identity/NMI behavior, hostNetwork usage, network controls, and node access first. If a node identity has broad Azure permissions, node compromise can become an Azure pivot even when application Workload ID is correctly scoped.
## References
- [https://cloud.google.com/kubernetes-engine/docs/how-to/workload-identity](https://cloud.google.com/kubernetes-engine/docs/how-to/workload-identity)
- [https://medium.com/zeotap-customer-intelligence-unleashed/gke-workload-identity-a-secure-way-for-gke-applications-to-access-gcp-services-f880f4e74e8c](https://medium.com/zeotap-customer-intelligence-unleashed/gke-workload-identity-a-secure-way-for-gke-applications-to-access-gcp-services-f880f4e74e8c)
- [https://blogs.halodoc.io/iam-roles-for-service-accounts-2/](https://blogs.halodoc.io/iam-roles-for-service-accounts-2/)
- [https://learn.microsoft.com/en-us/azure/aks/concepts-identity](https://learn.microsoft.com/en-us/azure/aks/concepts-identity)
- [https://learn.microsoft.com/en-us/azure/aks/workload-identity-overview](https://learn.microsoft.com/en-us/azure/aks/workload-identity-overview)
- [https://learn.microsoft.com/en-us/azure/aks/entra-id-authorization](https://learn.microsoft.com/en-us/azure/aks/entra-id-authorization)
{{#include ../../banners/hacktricks-training.md}}
@@ -95,7 +95,7 @@ kubectl get pods --all-namespaces
[**From the docs:**](https://kubernetes.io/docs/reference/access-authn-authz/rbac/#rolebinding-and-clusterrolebinding) A **role binding grants the permissions defined in a role to a user or set of users**. It holds a list of subjects (users, groups, or service accounts), and a reference to the role being granted. A **RoleBinding** grants permissions within a specific **namespace** whereas a **ClusterRoleBinding** grants that access **cluster-wide**.
```yaml:RoleBinding
piVersion: rbac.authorization.k8s.io/v1
apiVersion: rbac.authorization.k8s.io/v1
# This role binding allows "jane" to read pods in the "default" namespace.
# You need to already have a Role named "pod-reader" in that namespace.
kind: RoleBinding
@@ -132,6 +132,36 @@ roleRef:
**Permissions are additive** so if you have a clusterRole with “list” and “delete” secrets you can add it with a Role with “get”. So be aware and test always your roles and permissions and **specify what is ALLOWED, because everything is DENIED by default.**
### Details worth checking
RBAC uses resource names as they appear in API URLs, not the YAML `kind`. A Pod is `pods`, a Deployment is `deployments`, and subresources are written with a slash such as `pods/log`, `pods/exec`, `pods/portforward`, `pods/ephemeralcontainers`, `deployments/scale`, `serviceaccounts/token`, `nodes/proxy` or `services/proxy`. A permission on `pods` does not automatically grant access to `pods/exec` or `pods/log`.
`resourceNames` can restrict some requests to specific object names:
```yaml
rules:
- apiGroups: [""]
resources: ["configmaps"]
resourceNames: ["app-config"]
verbs: ["get", "update"]
```
This does not restrict top-level `create` or `deletecollection` by name. For `list` and `watch`, the client must include a matching `metadata.name` field selector, otherwise the request is not authorized by that rule:
```bash
kubectl get configmaps -n default --field-selector=metadata.name=app-config
```
Use exact access reviews for high-impact checks:
```bash
kubectl auth can-i create pods/exec -n default
kubectl auth can-i create serviceaccounts/token -n default
kubectl auth can-i impersonate users
kubectl auth can-i bind clusterroles.rbac.authorization.k8s.io
kubectl auth can-i escalate clusterroles.rbac.authorization.k8s.io
```
## **Enumerating RBAC**
```bash
@@ -164,5 +194,3 @@ abusing-roles-clusterroles-in-kubernetes/
{{#include ../../banners/hacktricks-training.md}}
@@ -6,11 +6,20 @@
## Definition
ValidatingWebhookConfiguration is a Kubernetes resource that defines a validating webhook, which is a server-side component that validates incoming Kubernetes API requests against a set of predefined rules and constraints.
`ValidatingWebhookConfiguration` is a Kubernetes resource that registers one or more validating admission webhooks. These webhooks receive AdmissionReview requests from the API server after authentication and authorization, but before the object is persisted.
Validating webhooks can reject a request. Mutating webhooks, configured with `MutatingWebhookConfiguration`, can change the object first. Security reviews should usually inspect both resources because a malicious or weak mutating webhook can rewrite workloads, while a validating webhook or policy engine can block or allow them.
## Purpose
The purpose of a ValidatingWebhookConfiguration is to define a validating webhook that will enforce a set of predefined rules and constraints on incoming Kubernetes API requests. The webhook will validate the requests against the rules and constraints defined in the configuration, and will return an error if the request does not conform to the rules.
The purpose of a `ValidatingWebhookConfiguration` is to define when the API server should call a validating webhook and how it should handle the webhook result. The important security question is not only "is a policy installed?", but also:
- Which API groups, resources, operations, and scopes does it match?
- Which namespaces or objects are excluded by selectors?
- Does `matchConditions` skip any request classes?
- Does `failurePolicy` fail open with `Ignore` or fail closed with `Fail`?
- Is the webhook service reachable, trusted by the configured `caBundle`, and run by a highly privileged service account?
- Does the policy engine also expose exception resources, excluded users, or excluded groups?
**Example**
@@ -21,22 +30,29 @@ apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
name: example-validation-webhook
namespace: default
webhook:
name: example-validation-webhook
clientConfig:
url: https://example.com/webhook
serviceAccountName: example-service-account
rules:
- apiGroups:
- ""
apiVersions:
- "*"
operations:
- CREATE
- UPDATE
resources:
- pods
webhooks:
- name: pods.example.local
admissionReviewVersions: ["v1"]
sideEffects: None
failurePolicy: Fail
timeoutSeconds: 5
clientConfig:
service:
namespace: webhook-system
name: example-validation-webhook
path: /validate
caBundle: <base64-ca-bundle>
rules:
- apiGroups: [""]
apiVersions: ["v1"]
operations: ["CREATE", "UPDATE"]
resources: ["pods"]
scope: "Namespaced"
namespaceSelector:
matchExpressions:
- key: kubernetes.io/metadata.name
operator: NotIn
values: ["kube-system"]
```
The main difference between a ValidatingWebhookConfiguration and policies :
@@ -49,9 +65,23 @@ The main difference between a ValidatingWebhookConfiguration and policies :
## Enumeration
```
$ kubectl get ValidatingWebhookConfiguration
$ kubectl get validatingwebhookconfigurations,mutatingwebhookconfigurations
$ kubectl get validatingwebhookconfiguration <name> -o yaml
$ kubectl get mutatingwebhookconfiguration <name> -o yaml
$ kubectl get svc,deploy,pod -A | grep -i webhook
```
Fields to inspect:
- `rules`: Check covered API groups, versions, resources, subresources, operations, and scope.
- `namespaceSelector` / `objectSelector`: Look for namespaces or labels that exclude resources from policy.
- `matchConditions`: CEL expressions can intentionally or accidentally skip requests.
- `failurePolicy`: `Ignore` lets requests continue if the webhook fails; `Fail` blocks them.
- `sideEffects`: Webhooks with side effects may not support dry-run testing.
- `timeoutSeconds`: Very short timeouts combined with `Ignore` can become fail-open behavior.
- `clientConfig`: Review whether the webhook points to an in-cluster Service or external URL, and inspect the backing workload and service account.
- `reinvocationPolicy`: Mutating webhooks may be reinvoked when later mutation changes the object.
### Abusing Kyverno and Gatekeeper VWC
As we can see all operators installed have at least one ValidatingWebHookConfiguration(VWC).
@@ -87,12 +117,23 @@ namespaceSelector:
- MYAPP
```
Here, `kubernetes.io/metadata.name` label refers to the namespace name. Namespaces with names in the `values` list will be excluded from the policy :
Here, `kubernetes.io/metadata.name` refers to the namespace name label. Namespaces with names in the `values` list will be excluded from the policy:
Check namespaces existence. Sometimes, due to automation or misconfiguration, some namespaces might have not been created. If you have permission to create namespace, you could create a namespace with a name in the `values` list and policies won't apply your new namespace.
The goal of this attack is to exploit **misconfiguration** inside VWC in order to bypass operators restrictions and then elevate your privileges with other techniques
Other common bypass or abuse patterns:
- An `objectSelector` that allows users to add an opt-out label to their own objects.
- `failurePolicy: Ignore` on security-critical validation, especially when the webhook Service has no endpoints or unreliable networking.
- Policy engine exceptions for users, groups, service accounts, namespaces, or roles that are broader than intended.
- Missing coverage for workload controller templates, `pods/ephemeralcontainers`, `pods/exec`, custom resources, or update operations.
- Write access to `validatingwebhookconfigurations`, `mutatingwebhookconfigurations`, Gatekeeper constraints, Kyverno policies, or exception resources.
- A malicious mutating webhook that injects containers, changes images, mounts secrets, adds tolerations, or changes service account selection before validation.
Remember that admission only protects requests that pass through the API server admission chain. Static Pods, node-local runtime socket access, direct kubelet abuse, and direct etcd access are different trust paths and need separate hardening and monitoring.
{{#ref}}
abusing-roles-clusterroles-in-kubernetes/
{{#endref}}
@@ -102,6 +143,8 @@ abusing-roles-clusterroles-in-kubernetes/
- [https://github.com/open-policy-agent/gatekeeper](https://github.com/open-policy-agent/gatekeeper)
- [https://kyverno.io/](https://kyverno.io/)
- [https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/)
- [https://kubernetes.io/docs/concepts/cluster-administration/admission-webhooks-good-practices/](https://kubernetes.io/docs/concepts/cluster-administration/admission-webhooks-good-practices/)
- [https://kubernetes.io/docs/reference/access-authn-authz/validating-admission-policy/](https://kubernetes.io/docs/reference/access-authn-authz/validating-admission-policy/)
@@ -8,6 +8,16 @@ Kubernetes uses several **specific network services** that you might find **expo
One way could be searching for `Identity LIKE "k8s.%.com"` in [crt.sh](https://crt.sh) to find subdomains related to kubernetes. Another way might be to search `"k8s.%.com"` in github and search for **YAML files** containing the string.
Useful external recon signals to correlate before scanning:
- DNS and certificate transparency names containing `k8s`, `kube`, `api`, `apiserver`, `eks`, `gke`, `aks`, `cluster`, `ingress`, `argocd`, `grafana`, `prometheus`, `harbor`, `registry`, `dashboard`, `dev`, `stage`, or region names.
- Cloud load balancer names, CNAMEs, tags, and provider hostnames that can link an exposed application or platform UI back to a cluster.
- Public repositories, CI logs, Helm values, Terraform state, rendered manifests, container images, and documentation leaking kubeconfigs, API server URLs, namespaces, service accounts, `type: LoadBalancer`, `type: NodePort`, Ingress hosts, Gateway listeners, or dashboard settings.
- Managed Kubernetes inventory, when cloud credentials are in scope: EKS endpoint public/private access and public CIDRs, GKE public/private control-plane settings and authorized networks, and AKS private cluster/API server authorized IP settings.
- Exposed platform tools around the cluster such as Argo CD, Prometheus, Grafana, Harbor, registries, CI/CD dashboards, service mesh dashboards, and ingress-controller admin or metrics endpoints.
Treat these as attribution and prioritization clues. A public Ingress application is normal in many clusters, while exposed kubelet, etcd, dashboard, CI/CD deploy control, or leaked kubeconfig material should be prioritized much higher.
## How Kubernetes Exposes Services
It might be useful for you to understand how Kubernetes can **expose services publicly** in order to find them:
@@ -126,6 +136,31 @@ When a port is exposed in all the nodes via a **NodePort**, the same port is ope
sudo nmap -sS -p 30000-32767 <IP>
```
### Service mesh and proxy surfaces
Clusters using **Istio, Linkerd, Cilium service mesh, or Envoy-based gateways** add another service layer to enumerate. A mesh can provide mTLS, workload identity, L7 routing, authorization policy, telemetry, and gateway/egress controls, but it only protects traffic that is actually enrolled and intercepted by the mesh.
Useful checks from Kubernetes access:
```bash
kubectl get ns --show-labels | egrep 'istio|linkerd|mesh|cilium'
kubectl get crd | egrep 'istio.io|linkerd.io|gateway.networking.k8s.io|cilium.io'
kubectl get mutatingwebhookconfiguration,validatingwebhookconfiguration | egrep 'istio|linkerd|cilium'
kubectl get pods -A -o custom-columns='NS:.metadata.namespace,NAME:.metadata.name,SA:.spec.serviceAccountName,CONTAINERS:.spec.containers[*].name'
kubectl get svc -A | egrep 'istio|envoy|linkerd|kiali|jaeger|prometheus|grafana|zipkin|hubble'
```
Review:
- Namespaces or workloads that opted out of injection, still run without a proxy, or were created before injection was enabled.
- mTLS mode. Permissive migration modes may still accept plaintext from unmeshed sources.
- Istio `PeerAuthentication`, `AuthorizationPolicy`, `RequestAuthentication`, gateways, waypoints, and egress resources.
- Linkerd policy resources, identity, Server/authorization objects, and exposed `linkerd-viz`, tap, or metrics surfaces.
- Cilium service mesh and Gateway API resources, Hubble visibility, Cilium policies, and Envoy integration points.
- Envoy admin, config dump, stats, metrics, tracing, dashboard, and debug endpoints. These can leak routes, upstreams, certificates, identity, and traffic state if exposed too broadly.
Do not treat service mesh as a replacement for Kubernetes RBAC or NetworkPolicies. A mesh policy can block an HTTP request while an unmeshed Pod, skipped port, direct Pod IP path, gateway, egress proxy, or missing NetworkPolicy still leaves a practical route.
## Vulnerable Misconfigurations
### Kube-apiserver Anonymous Access
@@ -212,5 +247,3 @@ https://labs.f-secure.com/blog/attacking-kubernetes-through-kubelet
{{#endref}}
{{#include ../../../banners/hacktricks-training.md}}