☸️ Kubernetes Security

Kubernetes Security Reference

Pod security, RBAC misconfigurations, container escape techniques, admission controllers, and kubectl security audit commands for cloud security teams.

πŸ” RBAC Misconfigurations

Kubernetes RBAC (Role-Based Access Control) misconfigurations are the most common path to cluster compromise. These are the most dangerous patterns.

Dangerous Permissions Reference

PermissionWhy DangerousEscalation PathSeverity
* / * / * (wildcard all)Full cluster admin β€” complete controlDirect cluster takeoverCritical
pods/execExecute commands in any podContainer escape β†’ node accessCritical
create pods (with hostPID/hostNetwork/hostPath)Create privileged pods on any nodeNode escape β†’ full clusterCritical
secrets get/list (cluster-wide)Read all secrets including service account tokensImpersonate any SA in clusterCritical
clusterroles/clusterrolebindings createCreate new cluster admin bindingsDirect privilege escalationCritical
nodes/proxy or pods/proxyProxy to any pod/node β€” bypasses network policiesAccess internal servicesHigh
impersonate users/groups/serviceaccountsAct as any user or SA in the clusterLateral movement to any identityHigh
daemonsets createRun privileged container on every node simultaneouslyFull cluster node compromiseHigh
validatingwebhookconfigurations createIntercept all API requests β€” man-in-the-middleData theft, policy bypassHigh
roles/rolebindings create (same namespace)Grant yourself any permissions within namespaceNamespace privilege escalationMedium

Find dangerous RBAC bindings in your cluster

#!/bin/bash # Find all cluster admin bindings echo "=== CLUSTER ADMIN BINDINGS ===" kubectl get clusterrolebindings -o json | python3 -c " import sys, json data = json.load(sys.stdin) for item in data['items']: rr = item.get('roleRef',{}) if rr.get('name') in ['cluster-admin','system:masters']: subjects = item.get('subjects',[]) for s in subjects: print(f'CLUSTER-ADMIN: {s.get(\"kind\")}/{s.get(\"name\")} via {item[\"metadata\"][\"name\"]}') " # Find bindings with wildcard verbs echo -e "\n=== WILDCARD PERMISSIONS ===" kubectl get clusterroles -o json | python3 -c " import sys, json data = json.load(sys.stdin) for role in data['items']: name = role['metadata']['name'] if name.startswith('system:'): continue for rule in role.get('rules',[]): if '*' in rule.get('verbs',[]) or '*' in rule.get('resources',[]): print(f'WILDCARD: {name} - verbs:{rule.get(\"verbs\")} resources:{rule.get(\"resources\")}') " # Find pods/exec permission echo -e "\n=== PODS/EXEC PERMISSIONS ===" kubectl get clusterroles,roles --all-namespaces -o json | \ python3 -c " import sys, json data = json.load(sys.stdin) for item in data.get('items',[]): for rule in item.get('rules',[]): if 'exec' in rule.get('resources',[]) or ('pods' in rule.get('resources',[]) and 'exec' in rule.get('verbs',[])): ns = item['metadata'].get('namespace','cluster') print(f'EXEC: {item[\"metadata\"][\"name\"]} in {ns}') "

Secure RBAC β€” Least Privilege Example

apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: app-reader namespace: production rules: - apiGroups: [""] resources: ["pods", "services", "configmaps"] verbs: ["get", "list", "watch"] # Read-only # NOT: create, delete, patch, exec, port-forward - apiGroups: ["apps"] resources: ["deployments", "replicasets"] verbs: ["get", "list"] # Explicitly no write permissions --- # NEVER DO THIS: # rules: # - apiGroups: ["*"] # resources: ["*"] # verbs: ["*"]

πŸ›‘ Pod Security

Pod security controls whether a container can access host resources. A misconfigured pod is the most common container escape vector.

Dangerous Pod Security Settings

SettingRiskEscape Path
privileged: trueCriticalContainer has full host capabilities β€” trivial escape to node
hostPID: trueCriticalSee all host processes β€” can inject into host process namespaces
hostNetwork: trueHighContainer on host network β€” bypass network policies, access cloud metadata
hostPath volume mountsCriticalMount host filesystem β€” read /etc/shadow, write cron, access node credentials
allowPrivilegeEscalation: true (default)Highsetuid binaries can escalate to root
runAsUser: 0 or no runAsNonRootMediumRunning as root β€” combined with other settings enables escape
capabilities: add: [SYS_ADMIN]CriticalSYS_ADMIN capability allows mount operations β€” enables escape
capabilities: add: [NET_ADMIN]MediumCan modify network interfaces and routing
serviceAccountToken: true (default) + cluster-admin SACriticalCompromised container gets cluster-admin token

Secure pod template β€” hardened baseline

apiVersion: v1 kind: Pod metadata: name: secure-app spec: automountServiceAccountToken: false # Don't mount SA token unless needed securityContext: runAsNonRoot: true # Never run as root runAsUser: 10001 # Specific non-root UID runAsGroup: 10001 fsGroup: 10001 seccompProfile: type: RuntimeDefault # Restrict syscalls with seccomp containers: - name: app image: your-registry/app:1.0.0 # Pin to specific digest, not :latest securityContext: privileged: false # Never privileged allowPrivilegeEscalation: false # Block setuid escalation readOnlyRootFilesystem: true # Immutable root FS capabilities: drop: [ALL] # Drop ALL capabilities add: [] # Add none back unless required # hostPID: false (default β€” do not set true) # hostNetwork: false (default β€” do not set true) # hostPath volumes: never use in production resources: limits: cpu: "500m" memory: "256Mi" requests: cpu: "100m" memory: "64Mi"

Pod Security Standards (PSS) β€” Kubernetes built-in

LevelWhat it restrictsUse for
privilegedNo restrictionsSystem components only (kube-system)
baselineBlocks most dangerous settings (privileged, hostPID, hostNetwork, hostPath)Default for all application namespaces
restrictedAll of baseline + requires non-root, drops capabilities, requires seccompSensitive workloads, PCI/PII processing
# Apply Pod Security Standards to a namespace kubectl label namespace production \ pod-security.kubernetes.io/enforce=restricted \ pod-security.kubernetes.io/warn=restricted \ pod-security.kubernetes.io/audit=restricted

🚨 Container Escape Techniques

For defensive knowledge only. Understanding these techniques helps detect and prevent them. All examples assume an attacker already has shell access inside a container.

1. Privileged Container Escape via /proc T1611

If a container runs as privileged, it can write to /proc/sysrq-trigger or use nsenter to break into host namespaces.
# Detection: Find privileged pods kubectl get pods --all-namespaces -o json | \ python3 -c " import sys,json for item in json.load(sys.stdin)['items']: for c in item['spec']['containers']: sc = c.get('securityContext',{}) if sc.get('privileged'): print(f'PRIVILEGED: {item[\"metadata\"][\"namespace\"]}/{item[\"metadata\"][\"name\"]}/{c[\"name\"]}') "

2. hostPath Volume Mount β†’ Node Filesystem Access T1611

Container with hostPath volume mount has read/write access to the node filesystem. Can read /etc/shadow, write to /etc/cron.d for persistence, or access kubelet credentials.
# Find pods with hostPath mounts kubectl get pods --all-namespaces -o json | python3 -c " import sys,json for item in json.load(sys.stdin)['items']: vols = item['spec'].get('volumes',[]) for v in vols: hp = v.get('hostPath',{}) if hp: print(f'HOSTPATH: {item[\"metadata\"][\"namespace\"]}/{item[\"metadata\"][\"name\"]} mounts {hp.get(\"path\")}') " # Dangerous hostPath mounts to look for: # path: / (entire root filesystem) # path: /etc (system configuration) # path: /var/lib/kubelet (kubelet credentials) # path: /proc (host process info) # path: /var/run/docker.sock (Docker socket β€” full container escape)

3. Docker Socket Mount β†’ Container Management T1610

Mounting /var/run/docker.sock or /run/containerd/containerd.sock into a container gives the container full control over the container runtime β€” can spawn new privileged containers with host mounts.
# Find containers with Docker socket mounted kubectl get pods --all-namespaces -o json | python3 -c " import sys,json for item in json.load(sys.stdin)['items']: for v in item['spec'].get('volumes',[]): path = v.get('hostPath',{}).get('path','') if 'docker.sock' in path or 'containerd.sock' in path: print(f'SOCKET MOUNT: {item[\"metadata\"][\"namespace\"]}/{item[\"metadata\"][\"name\"]} - {path}') "

4. Service Account Token Abuse β†’ API Server Access T1528

Every pod gets a service account token mounted at /var/run/secrets/kubernetes.io/serviceaccount/token by default. If the SA has cluster-wide permissions, this token provides broad API access.
# Check what a service account token can do # From inside a pod: TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token) kubectl --token=$TOKEN auth can-i --list # From outside (auditing): kubectl auth can-i --list --as=system:serviceaccount:NAMESPACE:SA_NAME # Find SAs with cluster-admin kubectl get clusterrolebindings -o json | python3 -c " import sys,json for item in json.load(sys.stdin)['items']: if item.get('roleRef',{}).get('name')=='cluster-admin': for s in item.get('subjects',[]): if s.get('kind')=='ServiceAccount': print(f'SA CLUSTER-ADMIN: {s.get(\"namespace\")}/{s.get(\"name\")}') "

5. IMDS Access from Container with hostNetwork T1552.005

Container with hostNetwork:true is on the node's network β€” can reach the cloud provider IMDS endpoint (169.254.169.254) directly, bypassing any network policy restrictions.
# Find pods with hostNetwork enabled kubectl get pods --all-namespaces -o json | python3 -c " import sys,json for item in json.load(sys.stdin)['items']: if item['spec'].get('hostNetwork'): print(f'HOST_NETWORK: {item[\"metadata\"][\"namespace\"]}/{item[\"metadata\"][\"name\"]}') " # In a container with hostNetwork β€” would reach node's IAM credentials: # curl http://169.254.169.254/latest/meta-data/iam/security-credentials/

🚦 Admission Controllers

Admission controllers intercept API requests before objects are persisted. They are the primary enforcement mechanism for security policies in Kubernetes.

ControllerWhat It DoesSecurity Value
OPA GatekeeperPolicy enforcement via Open Policy Agent. Rego-based policies evaluated for every API request.Essential β€” block privileged pods, enforce image registries, require labels
KyvernoKubernetes-native policy engine. YAML-based policies (easier than Rego). Validate, mutate, generate.Essential β€” simpler than Gatekeeper for most teams
PodSecurity (built-in)Enforces Pod Security Standards (privileged/baseline/restricted) at namespace level.High β€” built-in, no additional install needed
ImagePolicyWebhookValidate container images against an external policy server before allowing pod creation.High β€” enforce image scanning, registry allowlist
NodeRestrictionLimits what kubelet can modify β€” prevents nodes from elevating their own privileges.High β€” should always be enabled
AlwaysPullImagesForces container images to always be pulled from registry β€” prevents using cached malicious images.Medium β€” useful with private registry

Kyverno β€” Enforce no privileged containers (policy example)

apiVersion: kyverno.io/v1 kind: ClusterPolicy metadata: name: disallow-privileged-containers spec: validationFailureAction: Enforce # Enforce = block; Audit = warn only background: true rules: - name: check-privileged match: any: - resources: kinds: [Pod] validate: message: "Privileged containers are not allowed." pattern: spec: containers: - =(securityContext): =(privileged): "false" --- # Block hostPath mounts apiVersion: kyverno.io/v1 kind: ClusterPolicy metadata: name: disallow-host-path spec: validationFailureAction: Enforce rules: - name: no-hostpath match: any: - resources: kinds: [Pod] validate: message: "HostPath volumes are not permitted." deny: conditions: any: - key: "{{ request.object.spec.volumes[].hostPath | length(@) }}" operator: GreaterThan value: "0"

πŸ” Kubernetes Security Audit β€” kubectl Commands

One-liner cluster security assessment

#!/bin/bash # Quick Kubernetes security audit echo "======================================" echo "KUBERNETES SECURITY AUDIT" echo "Cluster: $(kubectl config current-context)" echo "======================================" echo -e "\n[1] CLUSTER VERSION" kubectl version --short 2>/dev/null echo -e "\n[2] PRIVILEGED PODS" kubectl get pods --all-namespaces -o json | python3 -c " import sys,json; d=json.load(sys.stdin) for i in d['items']: for c in i['spec'].get('containers',[])+i['spec'].get('initContainers',[]): if c.get('securityContext',{}).get('privileged'): print(f' {i[\"metadata\"][\"namespace\"]}/{i[\"metadata\"][\"name\"]}/{c[\"name\"]}') " || echo " None found" echo -e "\n[3] HOSTPATH VOLUMES" kubectl get pods --all-namespaces -o json | python3 -c " import sys,json; d=json.load(sys.stdin) for i in d['items']: for v in i['spec'].get('volumes',[]): if v.get('hostPath'): print(f' {i[\"metadata\"][\"namespace\"]}/{i[\"metadata\"][\"name\"]}: {v[\"hostPath\"][\"path\"]}') " || echo " None found" echo -e "\n[4] HOST NETWORK PODS" kubectl get pods --all-namespaces -o json | python3 -c " import sys,json; d=json.load(sys.stdin) [print(f' {i[\"metadata\"][\"namespace\"]}/{i[\"metadata\"][\"name\"]}') for i in d['items'] if i['spec'].get('hostNetwork')] " || echo " None found" echo -e "\n[5] CLUSTER ADMIN BINDINGS" kubectl get clusterrolebindings -o json | python3 -c " import sys,json; d=json.load(sys.stdin) for i in d['items']: if i.get('roleRef',{}).get('name')=='cluster-admin': for s in i.get('subjects',[]): print(f' {s.get(\"kind\")}/{s.get(\"name\")} via {i[\"metadata\"][\"name\"]}') " echo -e "\n[6] DEFAULT SA TOKENS AUTOMOUNTED" kubectl get pods --all-namespaces -o json | python3 -c " import sys,json; d=json.load(sys.stdin) count=sum(1 for i in d['items'] if i['spec'].get('automountServiceAccountToken',True)) print(f' {count} pods auto-mounting SA tokens') " echo -e "\n[7] CONTAINERS RUNNING AS ROOT" kubectl get pods --all-namespaces -o json | python3 -c " import sys,json; d=json.load(sys.stdin) for i in d['items']: for c in i['spec'].get('containers',[]): sc=c.get('securityContext',{}) psc=i['spec'].get('securityContext',{}) uid=sc.get('runAsUser',psc.get('runAsUser',None)) non_root=sc.get('runAsNonRoot',psc.get('runAsNonRoot',False)) if uid==0 or (uid is None and not non_root): print(f' {i[\"metadata\"][\"namespace\"]}/{c[\"name\"]}') " | head -20 echo -e "\n[8] EXTERNAL FACING SERVICES" kubectl get services --all-namespaces -o json | python3 -c " import sys,json; d=json.load(sys.stdin) for i in d['items']: if i['spec']['type'] in ['LoadBalancer','NodePort']: ip=i['spec'].get('loadBalancerIP') or i['status'].get('loadBalancer',{}).get('ingress',[{}])[0].get('ip','pending') print(f' {i[\"metadata\"][\"namespace\"]}/{i[\"metadata\"][\"name\"]} ({i[\"spec\"][\"type\"]}) IP:{ip}') "

Automated scanners

# kube-bench β€” CIS Kubernetes Benchmark docker run --pid=host --network=host --userns=host \ -v /etc:/etc:ro -v /var:/var:ro \ aquasec/kube-bench:latest # kube-hunter β€” threat hunting for K8s pip install kube-hunter kube-hunter --remote CLUSTER_IP # external scan kube-hunter --pod # scan from inside a pod # Trivy β€” container image scanning trivy image REGISTRY/IMAGE:TAG # Polaris β€” policy validation kubectl apply -f https://github.com/FairwindsOps/polaris/releases/latest/download/webhook.yaml

βš”οΈ MITRE ATT&CK for Containers

TacticTechniqueIDK8s Context
Initial AccessValid Accounts β€” Cloud AccountsT1078.004Stolen kubeconfig, RBAC misconfiguration, exposed API server
Initial AccessExploit Public-Facing ApplicationT1190Kubernetes API server exposed without auth, exposed dashboard
ExecutionDeploy ContainerT1610Attacker deploys malicious container via Docker socket or API
ExecutionContainer Administration CommandT1609kubectl exec, docker exec to run commands in existing container
PersistenceAccount ManipulationT1098Create new ClusterRoleBinding, new ServiceAccount with cluster-admin
PersistenceCreate AccountT1136Create new ServiceAccount, new kubeconfig credential
Privilege EscalationEscape to HostT1611Privileged container, hostPath mount, hostPID, Docker socket
Privilege EscalationAbuse Elevation Control MechanismT1548Create ClusterRoleBinding to cluster-admin from lesser-privileged SA
Defence EvasionImpair DefencesT1562Delete audit logs, disable admission controllers, remove DaemonSet agents
Credential AccessSteal Application Access TokenT1528Read SA tokens from /var/run/secrets/kubernetes.io/serviceaccount/
Credential AccessUnsecured Credentials: Cloud Instance MetadataT1552.005Access IMDS via hostNetwork pod or compromised node
DiscoveryNetwork Service DiscoveryT1046Scan cluster network from compromised pod
DiscoveryCloud Infrastructure DiscoveryT1580kubectl get all --all-namespaces to enumerate cluster
Lateral MovementUse Alternate Authentication MaterialT1550Use stolen SA token to access other namespaces or services
ExfiltrationTransfer Data to Cloud AccountT1537Exfiltrate PVC data, secrets, or config to external storage
ImpactResource HijackingT1496Crypto mining via deployed pods on cluster nodes
ImpactData DestructionT1485Delete namespaces, PVCs, deployments β€” cluster destruction