One Misconfiguration Away: How Root Containers Hand Attackers the Keys to Your Cluster + Video

Listen to this Post

Featured Image

Introduction:

In the race to deploy cloud-native applications, the path of least resistance often involves running containers with root privileges. This convenience, however, creates a catastrophic blind spot in your security posture. If a container running as root is compromised, the attacker inherits those elevated privileges, turning a simple application vulnerability into a direct line to the host kernel and potentially your entire Kubernetes cluster.

Learning Objectives:

  • Understand the attack surface created by privileged and root containers.
  • Learn to implement a defense-in-depth strategy using Pod Security Standards.
  • Gain hands-on skills to audit and harden Kubernetes pod specifications with Linux capabilities, seccomp, and read-only filesystems.

You Should Know:

1. The Anatomy of a Container Escape

A container is not a virtual machine; it is a collection of Linux processes isolated by namespaces and constrained by cgroups. When you run a container as the root user (UID 0) and grant it privileges, you are essentially telling the kernel that this process should have near-equal rights to the host itself.

If an attacker exploits a vulnerable application inside that pod (e.g., a remote code execution flaw), they are now root inside the container. From there, they can look for misconfigurations like mounted docker sockets or kernel exploits to break out.

Step‑by‑step guide: Simulating an Over-Privileged Container

Let’s see what access looks like from inside a default insecure pod.
1. Run a privileged container: This command gives the container access to all devices and disables namespace restrictions.

docker run --privileged -it ubuntu bash
 Inside the container
whoami
 Output: root

2. Check host device visibility: In a privileged container, you can see and interact with host disks.

ls -la /dev/ | grep sda
 You might see sda, sda1, sda2 (the host's actual disks)

3. Attempt host namespace interaction: You can often interact with host processes.

nsenter -t 1 -m -u -n -i sh
 If successful, you are now in the host's namespace.

2. Enforcing a Non-Root User

The first and most critical step is to prevent the container from running as root. Most base images (like ubuntu:latest) run as root by default. You must explicitly define a user.

Step‑by‑step guide: Implementing `runAsNonRoot`

1. Create a non-root user in your Dockerfile:

FROM ubuntu:22.04
RUN useradd -u 1000 -m appuser
USER appuser
COPY --chown=appuser:appuser app /app
CMD ["/app/start.sh"]

2. Enforce it in the Kubernetes Pod Spec: This Pod will be rejected if the image tries to run as root.

apiVersion: v1
kind: Pod
metadata:
name: secure-pod
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 3000
containers:
- name: my-app
image: my-secure-image:latest

3. Verification: If an image attempts to run as root with runAsNonRoot: true, Kubernetes will prevent the pod from starting, throwing a “container has runAsNonRoot and image will run as root” error.

3. Locking Down the Filesystem

If an attacker gets in, their first move is often to download malware or modify binaries to persist. Making the root filesystem read-only eliminates this option.

Step‑by‑step guide: Implementing `readOnlyRootFilesystem`

  1. Identify Write Paths: Your application likely needs to write to specific volumes (e.g., logs, temp files, uploads). You must identify these and mount writable volumes for them.

2. Apply the Pod Spec:

apiVersion: v1
kind: Pod
metadata:
name: read-only-pod
spec:
containers:
- name: app
image: nginx:latest
securityContext:
readOnlyRootFilesystem: true
volumeMounts:
- name: tmp-volume
mountPath: /tmp
- name: cache-volume
mountPath: /var/cache/nginx
volumes:
- name: tmp-volume
emptyDir: {}
- name: cache-volume
emptyDir: {}

3. Testing: Attempt to create a file outside of the mounted volumes.

kubectl exec -it read-only-pod -- touch /test.txt
 Expected output: touch: cannot touch '/test.txt': Read-only file system

4. Stripping Linux Capabilities

Linux capabilities break the “all or nothing” nature of root access. Even if your app runs as root, you can drop all capabilities except the absolute bare minimum it needs to function.

Step‑by‑step guide: Dropping All Capabilities

  1. The Principle of Least Privilege: Identify what syscalls your app needs. A typical web app needs networking (NET_BIND_SERVICE if binding to a port under 1024) and little else.
  2. Implementing drop: ["ALL"]: This removes all 40+ capabilities. You then only add back the strictly necessary ones.
    apiVersion: v1
    kind: Pod
    metadata:
    name: capability-hardened-pod
    spec:
    containers:</li>
    </ol>
    
    - name: app
    image: nginx:latest
    securityContext:
    capabilities:
    drop:
    - ALL
    add:
    - NET_BIND_SERVICE  Required for binding to port 80
    

    3. Verification with `capsh`:

     Exec into the pod
    kubectl exec -it capability-hardened-pod -- bash
     Install capsh if needed: apt update && apt install -y libcap2-bin
    capsh --print
     The Current: list should be very short, likely only "cap_net_bind_service"
    

    5. Applying a Seccomp Profile

    Seccomp (secure computing mode) filters system calls. While dropping capabilities removes coarse privileges, seccomp blocks the actual kernel functions an attacker might try to use for an escape.

    Step‑by‑step guide: Enabling the Runtime Default Profile

    For most workloads, the default seccomp profile provided by the container runtime (like containerd) blocks roughly 300+ dangerous syscalls while allowing normal application operations.

    1. Enable the Profile:

    apiVersion: v1
    kind: Pod
    metadata:
    name: seccomp-pod
    spec:
    securityContext:
    seccompProfile:
    type: RuntimeDefault
    containers:
    - name: app
    image: nginx:latest
    

    2. Why this matters: Syscalls like unshare, mount, and `ptrace` are often blocked by the default profile, effectively neutralizing many container escape exploits (e.g., CVE-2016-5195) that rely on them, even if the container is running as root.

    1. Putting It All Together: The Hardened Pod Spec
      A secure pod combines all these elements. Here is a production-ready example that aligns with the “Restricted” Pod Security Standard.
    apiVersion: v1
    kind: Pod
    metadata:
    name: fully-hardened-pod
    spec:
    securityContext:
    runAsNonRoot: true
    runAsUser: 1001
    runAsGroup: 2001
    fsGroup: 2001
    seccompProfile:
    type: RuntimeDefault
    containers:
    - name: app
    image: my-app:latest
    securityContext:
    allowPrivilegeEscalation: false
    privileged: false
    readOnlyRootFilesystem: true
    capabilities:
    drop: ["ALL"]
    add: ["NET_BIND_SERVICE"]  Only if necessary
    volumeMounts:
    - name: tmp-volume
    mountPath: /tmp
    volumes:
    - name: tmp-volume
    emptyDir: {}
    

    7. Auditing Your Cluster for Insecure Pods

    You need to identify existing risks. Use `kubectl` to audit your running workloads.

    Linux Command to find privileged pods:

    kubectl get pods --all-namespaces -o json | jq '.items[] | select(.spec.containers[].securityContext.privileged==true) | .metadata.namespace + "/" + .metadata.name'
    

    Using `kubectl` with `wide` output to check user IDs:

     This requires a tool like 'kubectl-get-all' or iterating, but you can check describe
    kubectl describe pod <pod-name> | grep "User"
     Or check the pod's security context directly
    kubectl get pod <pod-name> -o yaml | grep -A 5 "securityContext"
    

    What Undercode Say:

    • Key Takeaway 1: Default is Danger. The default security context in Kubernetes is optimized for compatibility, not security. Assuming a default pod is safe is a critical error in risk assessment.
    • Key Takeaway 2: Defense in Depth is Mandatory. Relying on a single control like `runAsNonRoot` is insufficient. Combining user restrictions, a read-only filesystem, dropped capabilities, and a seccomp profile creates multiple barriers that an attacker must bypass, exponentially increasing the difficulty of a successful cluster compromise.

    The security of your containerized infrastructure is written in YAML. Treat your pod specs as the first line of defense in a zero-trust architecture. By shifting left and enforcing these policies at the admission controller level (using OPA/Gatekeeper or Kyverno), you can prevent these misconfigurations from ever reaching production, ensuring that even if an application is breached, the cluster itself remains intact.

    Prediction:

    As cloud-native adoption matures, we will see a shift from “shift-left” solely focusing on code vulnerabilities to “shift-left” focusing on infrastructure configuration. The next generation of Kubernetes attacks will not target the kernel, but the misconfigurations in pod security contexts. Consequently, automated policy-as-code engines will become as critical to the software development lifecycle as CI/CD pipelines themselves, with organizations treating a violation of `privileged: true` with the same severity as a critical library vulnerability.

    ▶️ Related Video (82% Match):

    🎯Let’s Practice For Free:

    IT/Security Reporter URL:

    Reported By: Codeanddecode Kubernetes – Hackers Feeds
    Extra Hub: Undercode MoN
    Basic Verification: Pass ✅

    🔐JOIN OUR CYBER WORLD [ CVE News • HackMonitor • UndercodeNews ]

    💬 Whatsapp | 💬 Telegram

    📢 Follow UndercodeTesting & Stay Tuned:

    𝕏 formerly Twitter 🐦 | @ Threads | 🔗 Linkedin | 🦋BlueSky