create pods
Standard pods with no host namespaces still allow serviceAccountName token theft, cloud IMDS access from the pod network, and, if admission controls permit, hostNetwork set to true to bypass all NetworkPolicies.
- API Group
- (core)
- Scope
- namespaced
- Audit Level
- Request
Escalation Paths
Create a workload with serviceAccountName set to a privileged ServiceAccount in the same namespace.
Any controller-backed kind works, since the controller creates the pod.
The token is mounted at /var/run/secrets/kubernetes.io/serviceaccount/token, or projected explicitly by the manifest.
Reading it back needs a channel.
get on pods/log returns it if the container prints it to stdout.
Without pods/log, write it to /dev/termination-log and read terminated.message from the pod's status, which needs get on pods.
Neither verb is needed if the container sends the token outbound itself, which is why removing pods/log does not close this.
Authenticate as the ServiceAccount with the stolen token.
Additional rights needed:
Create a PriorityClass with a very high value and preemptionPolicy PreemptLowerPriority.
Create a pod referencing that PriorityClass, with requests large enough that it only fits by evicting a running pod.
The scheduler preempts a lower-priority victim to make room.
This crosses namespaces, because priority is compared node-wide and the scheduler has no notion of which namespace owns the capacity.
The gain is scheduled capacity taken from another team, not credentials or API permissions.
Additional rights needed:
· both; a PriorityClass preempts nothing until a workload references it, and any controller-backed workload verb does instead of pods
Create a hostPath PersistentVolume mounting node / (or use an existing one via a PVC)
Claim the volume from an otherwise restricted-compliant pod.
PSA does not inspect the PV behind a PVC, so even the restricted profile does not block it
Read or write arbitrary node filesystem paths.
This is T0 on control plane nodes
Or set the PV's claimRef to a victim's PVC name and namespace, including one not yet created, so the victim's future workload silently binds to attacker-controlled storage
Additional rights needed:
· create the volume, claim it, mount it
· only where a hostPath PersistentVolume already exists
Note:
The base hostPath PV path is not version-patched, and Pod Security Admission does not block it at any profile.
PSA checks the pod's own volume types, never the PV behind a PVC.
runAsNonRoot only limits which host files are readable, not the mount.
CVE-2021-25741 is a separate, patched issue.
Create a pod with spec.hostNetwork set to true.
The pod shares the node's network namespace and uses the node's address, so it is no longer on the pod network.
NetworkPolicy selects pods by their pod-network identity, so an ordinary namespace policy does not constrain it.
Some CNIs provide a separate host-level firewall that can.
Node-local services become reachable, including the kubelet on 10250 and anything bound to the node's loopback or link-local addresses.
The dependable control is Pod Security Admission at baseline or restricted, which rejects spec.hostNetwork at admission.
Additional rights needed:
Note:
Namespace NetworkPolicy does not select host-network pods.
Whether host traffic can be policed at all is a property of the CNI, not of Kubernetes.
The effective control is Pod Security Admission at baseline or restricted, which rejects spec.hostNetwork.
Create a kubernetes.io/service-account-token Secret annotated with the target ServiceAccount name.
The token controller populates .data.token with a signed JWT for that ServiceAccount which does not expire.
Population happens after the create response returns, so the token is never in the create result.
Retrieval therefore needs get, list, or watch on secrets, or a pod that mounts the Secret.
create alone mints a credential the caller cannot read.
Authenticate as the target ServiceAccount with the retrieved token.
Additional rights needed:
· create mints the token, get reads it back by name
· list returns the data without needing the name
· watch catches the token as the controller fills it in
· a pod that mounts the Secret reads it instead
Note:
The token controller still populates kubernetes.io/service-account-token Secrets created by hand, and long-lived token Secrets remain supported.
Restrict create on secrets and audit for hand-made token Secrets in kube-system.
Create a RuntimeClass whose scheduling.tolerations and scheduling.nodeSelector target a control-plane node.
Create a pod that sets only runtimeClassName.
The RuntimeClass admission plugin merges the toleration and nodeSelector into the pod spec during that same request.
The pod's own manifest carries neither field, so manifest review, GitOps diffs, and static scanning miss it.
Validating admission does see the merged spec and can still catch it.
Placement alone is a scheduling-policy bypass.
A standard pod sitting on a control-plane node reads nothing it could not read elsewhere.
It becomes host compromise only with a pod spec that reaches the host, such as privileged or a hostPath mount of /etc/kubernetes.
Additional rights needed:
· both; the RuntimeClass schedules nothing on its own
