☸️ 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
| Permission | Why Dangerous | Escalation Path | Severity |
|---|---|---|---|
* / * / * (wildcard all) | Full cluster admin — complete control | Direct cluster takeover | Critical |
pods/exec | Execute commands in any pod | Container escape → node access | Critical |
create pods (with hostPID/hostNetwork/hostPath) | Create privileged pods on any node | Node escape → full cluster | Critical |
secrets get/list (cluster-wide) | Read all secrets including service account tokens | Impersonate any SA in cluster | Critical |
clusterroles/clusterrolebindings create | Create new cluster admin bindings | Direct privilege escalation | Critical |
nodes/proxy or pods/proxy | Proxy to any pod/node — bypasses network policies | Access internal services | High |
impersonate users/groups/serviceaccounts | Act as any user or SA in the cluster | Lateral movement to any identity | High |
daemonsets create | Run privileged container on every node simultaneously | Full cluster node compromise | High |
validatingwebhookconfigurations create | Intercept all API requests — man-in-the-middle | Data theft, policy bypass | High |
roles/rolebindings create (same namespace) | Grant yourself any permissions within namespace | Namespace privilege escalation | Medium |
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
| Setting | Risk | Escape Path |
|---|---|---|
privileged: true | Critical | Container has full host capabilities — trivial escape to node |
hostPID: true | Critical | See all host processes — can inject into host process namespaces |
hostNetwork: true | High | Container on host network — bypass network policies, access cloud metadata |
hostPath volume mounts | Critical | Mount host filesystem — read /etc/shadow, write cron, access node credentials |
allowPrivilegeEscalation: true (default) | High | setuid binaries can escalate to root |
runAsUser: 0 or no runAsNonRoot | Medium | Running as root — combined with other settings enables escape |
capabilities: add: [SYS_ADMIN] | Critical | SYS_ADMIN capability allows mount operations — enables escape |
capabilities: add: [NET_ADMIN] | Medium | Can modify network interfaces and routing |
serviceAccountToken: true (default) + cluster-admin SA | Critical | Compromised 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
| Level | What it restricts | Use for |
|---|---|---|
privileged | No restrictions | System components only (kube-system) |
baseline | Blocks most dangerous settings (privileged, hostPID, hostNetwork, hostPath) | Default for all application namespaces |
restricted | All of baseline + requires non-root, drops capabilities, requires seccomp | Sensitive 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.
| Controller | What It Does | Security Value |
|---|---|---|
| OPA Gatekeeper | Policy enforcement via Open Policy Agent. Rego-based policies evaluated for every API request. | Essential — block privileged pods, enforce image registries, require labels |
| Kyverno | Kubernetes-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 |
| ImagePolicyWebhook | Validate container images against an external policy server before allowing pod creation. | High — enforce image scanning, registry allowlist |
| NodeRestriction | Limits what kubelet can modify — prevents nodes from elevating their own privileges. | High — should always be enabled |
| AlwaysPullImages | Forces 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
| Tactic | Technique | ID | K8s Context |
|---|---|---|---|
| Initial Access | Valid Accounts — Cloud Accounts | T1078.004 | Stolen kubeconfig, RBAC misconfiguration, exposed API server |
| Initial Access | Exploit Public-Facing Application | T1190 | Kubernetes API server exposed without auth, exposed dashboard |
| Execution | Deploy Container | T1610 | Attacker deploys malicious container via Docker socket or API |
| Execution | Container Administration Command | T1609 | kubectl exec, docker exec to run commands in existing container |
| Persistence | Account Manipulation | T1098 | Create new ClusterRoleBinding, new ServiceAccount with cluster-admin |
| Persistence | Create Account | T1136 | Create new ServiceAccount, new kubeconfig credential |
| Privilege Escalation | Escape to Host | T1611 | Privileged container, hostPath mount, hostPID, Docker socket |
| Privilege Escalation | Abuse Elevation Control Mechanism | T1548 | Create ClusterRoleBinding to cluster-admin from lesser-privileged SA |
| Defence Evasion | Impair Defences | T1562 | Delete audit logs, disable admission controllers, remove DaemonSet agents |
| Credential Access | Steal Application Access Token | T1528 | Read SA tokens from /var/run/secrets/kubernetes.io/serviceaccount/ |
| Credential Access | Unsecured Credentials: Cloud Instance Metadata | T1552.005 | Access IMDS via hostNetwork pod or compromised node |
| Discovery | Network Service Discovery | T1046 | Scan cluster network from compromised pod |
| Discovery | Cloud Infrastructure Discovery | T1580 | kubectl get all --all-namespaces to enumerate cluster |
| Lateral Movement | Use Alternate Authentication Material | T1550 | Use stolen SA token to access other namespaces or services |
| Exfiltration | Transfer Data to Cloud Account | T1537 | Exfiltrate PVC data, secrets, or config to external storage |
| Impact | Resource Hijacking | T1496 | Crypto mining via deployed pods on cluster nodes |
| Impact | Data Destruction | T1485 | Delete namespaces, PVCs, deployments — cluster destruction |