Skip to content

The pod was never truly isolated.

"Just a container" is a comforting idea, but it’s usually wrong. Once someone gains execution inside a pod, the real question isn’t whether they can escape but whether the cluster has already given the workload.

Luis Tangui Arias at UAPA presentation

People like to call a compromised container “just a container”, as if that settles the risk. The reasoning goes: the workload sits in its own namespace, so the blast radius is contained, and the host stays safe because the app never left its pod.

That assumption is only valid if you have actually carried out the test.

A pod is not an isolated island within a Kubernetes cluster because it has an execution context, mounted filesystems, environment variables, DNS visibility, network reachability, runtime permissions, a namespace context, and, in most cases, a Kubernetes identity mounted directly into the container.

From a Red Team perspective, the first question after gaining execution inside a pod is not always whether you can escape the container. Although this matters, it is not always the quickest or most direct way to make an impact.

A simpler question to ask: what has the cluster already provided for this workload?

In most environments, the boundary is weak long before anyone tries to break it, through identity, RBAC, mounted volumes, runtime privileges, or host namespace exposure.

The pod was never really isolated; it just inherited whatever the cluster’s configuration allowed.

Starting from inside the pod

After gaining command execution inside a pod, the first step is to work out its position. To do this, determine what the workload can see, what it can access, and what identity or permissions are available from that context.

No sophisticated tooling is needed, since basic commands are usually enough to determine whether the pod has been given useful trust within the cluster.

id
whoami
hostname
env
mount
cat /proc/1/cgroup
cat /etc/resolv.conf
ls -la /var/run/secrets/kubernetes.io/serviceaccount/

 

These commands help answer the initial questions. Which user is running inside the container? Is the workload running as root? What paths are mounted? What DNS configuration is available? Which namespace is the pod running in? Is there Kubernetes identity material present?

At this point, the objective is not to escape. The goal is to understand the operating position.

The effects of a compromised pod with no useful identity, no sensitive mounts, and only limited network reachability differ greatly from those of a pod with API access, secret reads, host mounts, privileged mode, and HostPID and HostNetwork.

The token wasn’t just a file.

A file that is among the most relevant in a compromised pod is the ServiceAccount token; it is usually located at:

/var/run/secrets/kubernetes.io/serviceaccount/token

 

It is often treated as a Kubernetes implementation detail, but it is, in fact, workload identity.

If the token is present and valid, you can use it to hit the Kubernetes API Server directly; no kubectl needed. The Kubernetes API is reachable over plain HTTP, and the token works fine as a bearer credential.

TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
NAMESPACE=$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace)
CACERT=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
APISERVER=https://kubernetes.default.svc

 

Next: validate whether the API Server is even reachable from the pod.

curl -s --cacert “$CACERT”
-H “Authorization: Bearer $TOKEN”
“$APISERVER/version”

 

If that request succeeds, the pod is more than just a process in a container. It’s now a Kubernetes identity talking to the control plane.

The token specifies who the workload is, while RBAC specifies what that identity can do.

RBAC decides the impact

A ServiceAccount token is not automatically critical; its value depends on the permissions associated with it.

In the lab scenario, the ServiceAccount had been deliberately configured with read access to pods, services, configmaps, and secrets.

resources: [“pods”, “services”, “configmaps”, “secrets”]
verbs: [“get”, “list”, “watch”]

 

At first glance, this does not look like a destructive role. It does not include create, update, patch, or delete. It cannot create new pods, modify workloads, or change RBAC bindings.

However, it can read secrets.

That is enough to change the impact.

If the compromised pod can list and read Kubernetes Secrets, the attacker could obtain database authentication data, API keys, registry credentials, application secrets, service tokens, connection strings, or any other sensitive information stored in the namespace.

curl -s --cacert “$CACERT” 
-H “Authorization: Bearer $TOKEN” 
“$APISERVER/api/v1/namespaces/$NAMESPACE/secrets”

 

You can retrieve a specific secret the same way.

curl -s --cacert “$CACERT”
-H “Authorization: Bearer $TOKEN”
“$APISERVER/api/v1/namespaces/$NAMESPACE/secrets/db-prod-credentials”

If the value is base64-encoded, it can be decoded locally.

curl -s --cacert “$CACERT”
-H “Authorization: Bearer $TOKEN”
“$APISERVER/api/v1/namespaces/$NAMESPACE/secrets/db-prod-credentials”
| jq -r ‘.data.password’ | base64 -d

 

The attacker did not break RBAC. The attacker used RBAC.

This distinction matters in a Red Team assessment: it is not enough to say a token exists existswithin the pod, since the finding is what privileges that token confers once the application workload is compromised.

AttackerPod: the compromised workload

The first scenario is the compromised workload, represented as an AttackerPod.

This pod represents the initial position after obtaining execution inside an application container. From there, the following steps are enumeration, ServiceAccount discovery, API Server interaction, and permission validation.

The flow starts with the local context.

id
env
mount
cat /proc/1/cgroup
ls -la /var/run/secrets/kubernetes.io/serviceaccount/

 

If ServiceAccount material is present, the next step is to use that identity against the API Server.

curl -s --cacert “$CACERT”
-H “Authorization: Bearer $TOKEN”
“$APISERVER/api/v1/namespaces/$NAMESPACE/pods”

If RBAC allows access to secrets, the compromise moves beyond local command execution.

curl -s --cacert “$CACERT”
-H “Authorization: Bearer $TOKEN”
“$APISERVER/api/v1/namespaces/$NAMESPACE/secrets”/pre>

 

That’s what makes the pod worth targeting: it sits at the point where application runtime, Kubernetes identity, network access, and operational trust all overlap.

That is why you should not see a pod compromise as just container access. The real question is what the workload can do from there.

HostPath: when the host enters the container

The second scenario is HostPath.

A hostPath volume allows a pod to mount part of the node filesystem into the container. Legitimate uses include infrastructure components, security agents, storage components, or node-level tooling.

The problem occurs when application workloads have access to sensitive host paths.

A dangerous example is mounting the host root filesystem.

volumes:
name: host-root
hostPath:
path: /
type: Directory

 

If the attacker mounts that path inside the container at /host, they can inspect host files from the pod.

volumeMounts:
name: host-root
mountPath: /host

 

Once inside the pod, the mounted filesystem can be reviewed directly.

ls -la /host
cat /host/etc/os-release
cat /host/etc/passwd
cat /host/etc/shadow 2>/dev/null | head
ls -la /host/var/lib/kubelet
ls -la /host/etc/rancher/k3s

 

This is not a traditional container escape. The attacker did not exploit the runtime or the kernel to reach the host filesystem. The configuration exposed the host filesystem.

From an offensive position, HostPath can provide access to host-level context, kubelet data, runtime artifacts, logs, service configuration, and potentially sensitive files written to disk.

The effect varies by the path being mounted, whether the mount is read-only or read-write, which user runs inside the container, and what extra privileges are available. A read-only mount can still lead to sensitive information being exposed. A read-write mount can in turn increase the range of possible scenarios by enabling the modification of files accessible from the host.

Privileged: elevated runtime access

The third scenario is privileged: true.

A privileged container does not always result in immediate host compromise. The outcome is dependent on the runtime, kernel, node configuration, filesystem layout, mounted devices, and the privileges available inside the container.

What matters is that privileged mode substantially reduces the separation between the container and the host.

In this scenario, the attacker has obtained elevated privileges inside the container and is effectively operating as root within the container context. This matters because minimal images regularly lack tools such as debugfs, lsblk, capsh, or other utilities commonly used for system inspection.

First, inspect the current privileges and exposed devices.

id
grep Cap /proc/1/status
ls -la /dev
cat /proc/partitions

 

If the image is Alpine-based and you can install packages, the required tooling can be installed during post-exploitation.

apk update
apk add --no-cache util-linux e2fsprogs e2fsprogs-extra libcap

 

After installation, capabilities and visible block devices can be reviewed.

capsh --print
lsblk -o NAME,SIZE,FSTYPE,TYPE,MOUNTPOINTS,LABEL
cat /proc/partitions
ls -la /dev | grep -E ‘sd|vd|xvd|nvme|mmc|loop’

 

The exact device name depends on the environment. On x86_64 systems, it may be /dev/vda1, /dev/sda1, or /dev/nvme0n1p1. On Raspberry Pi systems, it may be something like /dev/mmcblk0p2.

In a controlled lab, debugfs can be used in a read-only manner to demonstrate host filesystem visibility through an exposed block device.

debugfs -R “cat /etc/os-release” /dev/vda1
debugfs -R “cat /etc/hostname” /dev/vda1

 

The objective of this test is not to modify the host filesystem. The objective is to demonstrate that the privileged container may have enough visibility into host-level storage to inspect filesystem content through exposed devices.

This does not mean every privileged pod will behave the same way. Filesystem type, device exposure, mount state, runtime configuration, kernel settings, and node layout all matter.

The key point is that once a container is both privileged and compromised with root-level execution, the attacker may be able to bring additional tooling into the environment and interact with host-exposed resources that aren’t available to a normal application workload.

HostPID: host process namespace visibility

The fourth scenario is HostPID.

When hostPID: true is enabled, the pod joins the host PID namespace. This means the container can see processes running on the node, not only processes inside its own isolated process namespace.

A simplified configuration looks like this.

spec:
hostPID: true
containers:
- name: hostpid-demo
image: alpine:3.20
securityContext:
privileged: true

 

Once inside the pod, process visibility changes.

ps aux | head -30
ls -la /proc/1
cat /proc/1/status | head

 

If the pod is also privileged and the required tooling is available, nsenter can be used to interact with host namespaces in a controlled way.

apk add --no-cache util-linux procps
nsenter -t 1 -m -u -i -n -p – sh -c ‘hostname; cat /etc/os-release; id’

 

HostPID isn’t simply about seeing more processes. Combined with privileged mode, it can provide a way to inspect and interact with host-level namespace context pertaining to inside the container.

From a Red Team perspective, this helps with host process discovery, namespace validation, and understanding how much of the node’s execution context is exposed to the workload.

HostNetwork: host network namespace visibility

The fifth scenario is HostNetwork.

When hostNetwork: true is enabled, the pod uses the node’s network namespace instead of the normal pod network namespace. This changes the workload’s network position.

A simplified configuration looks like this.

spec:
hostNetwork: true
containers:
- name: hostnetwork-demo
image: alpine:3.20
securityContext:
privileged: true

 

Inside the pod, the network view is different from a standard application pod.

ip addr
ip route

 

Whether the container has privileged status and the necessary tooling is available, the pod could possibly be able to examine network behaviour from a host level, on the basis of interface access and runtime restrictions.

apk add --no-cache tcpdump iproute2 bind-tools curl
tcpdump -i any -nn port 53 -c 5
nslookup kubernetes.default.svc.cluster.local
curl -s https://example.com > /tmp/outbound-test.txt

 

HostNetwork changes the pod’s isolation and visibility model. It can help an attacker understand host-level network configuration, routes, DNS behavior, service reachability, and, in some cases, traffic visibility.

From an offensive point of view, this is useful since the workload is no longer carrying out its operations just from within the standard pod network setup; instead it is doing so from the node’s network namespace.

The attack path is not always about escaping the container.

Container escape gets attention for good reason. Reaching the host from a container is serious.

However, in Kubernetes environments, escape is not always the first or most realistic path to impact.

Sometimes the path is based on identity.

pod -> token -> API Server -> secrets -> credentials -> impact

 

Sometimes it is based on host filesystem exposure.

pod -> HostPath -> host filesystem visibility

 

Sometimes it is based on elevated runtime permissions.

pod -> privileged -> devices and capabilities -> host-level visibility

 

Sometimes it is based on process namespace exposure.

pod -> HostPID -> host process visibility

 

Sometimes it is based on network positioning.

pod -> HostNetwork -> host network namespace visibility

 

An attacker does not need every misconfiguration. One useful path is often enough.

This is why assessments should not focus only on whether container escape is possible. The better question is whether a compromised workload can use its assigned trust to cause impact.

Final thoughts

A pod compromise is not just about the container filesystem. It is about the trust given to that workload.

That trust may come from a ServiceAccount token. It may come from RBAC permissions. It may come from mounted host paths. It may come from privileged mode. It may come from HostPID, HostNetwork, or access to internal services that were never expected to receive traffic from an attacker-controlled workload.

In a Red Team assessment, the pod matters because it is the starting point, not the boundary of the incident.

Once execution is gained, the next step is to determine what the pod is capable of within the cluster. Can it authenticate with the API Server? Can it read the secrets? Can it access the host files? Can it examine the host processes? Can it operate from the node network namespace? Can it reach services that trust internal traffic? Can it turn a local foothold into credentials, visibility, or control?

That is where the real attack path starts.

Not with the assumption that the container is isolated, nor with the assumption that the namespace is enough, nor that read-only access is harmless.

The pod was never truly isolated; it was part of the trust model.


Watch the talk

This topic was also presented live in Spanish by Luis Tangui Arias at Universidad Abierta Para Adultos (UAPA): “The Pod Was Never Isolated: Lecciones ofensivas al romper entornos contenerizados.”

Note: this video is in Spanish.

Watch on YouTube