AnalysisType: rule
RuleID: "Kubernetes.Pod.StealServiceAccountToken"
DisplayName: "Kubernetes Service Account Token Theft from Pod"
Enabled: true
Filename: k8s_steal_serviceaccount_token.py
LogTypes:
- Amazon.EKS.Audit
- Azure.MonitorActivity
- GCP.AuditLog
Tags:
- Kubernetes
- Credential Access
- Privilege Escalation
- Lateral Movement
- Unified Detection
Severity: High
Description: >
This detection monitors for commands executed in pods that attempt to read service account tokens from
/var/run/secrets/kubernetes.io/serviceaccount/token. Attackers who gain exec access to a pod can steal
its service account token to authenticate as that service account to the Kubernetes API server. This
enables privilege escalation and lateral movement within the cluster. This is a known attack technique
documented by Stratus Red Team.
Runbook: |
1. Immediately investigate the user executing commands in the pod and determine if they are authorized to access this pod
2. Review the service account permissions for this pod to assess what privileges the attacker could gain with the stolen token
3. Search for API operations using the stolen service account token in the past 24 hours by filtering audit logs for this service account principal
Reports:
Stratus Red Team:
- k8s.credential-access.steal-serviceaccount-token
MITRE ATT&CK:
- TA0006:T1552.007 # Credential Access: Unsecured Credentials - Container API
- TA0004:T1078.004 # Privilege Escalation: Valid Accounts - Cloud Accounts
- TA0008:T1550.001 # Lateral Movement: Use Alternate Authentication Material - Application Access Token
Reference: >
- https://stratus-red-team.cloud/attack-techniques/kubernetes/k8s.credential-access.steal-serviceaccount-token/
- https://medium.com/google-cloud/from-whoami-to-whoarewe-with-gke-workload-identity-for-fleets-3795ee942187
- https://dev.to/piyushjajoo/understanding-kubernetes-projected-service-account-tokens-205f
DedupPeriodMinutes: 60
SummaryAttributes:
- username
- namespace
- name
- p_source_label
Tests:
- Name: EKS cat service account token
ExpectedResult: true
Log:
{
"kind": "Event",
"apiVersion": "audit.k8s.io/v1",
"verb": "create",
"user": {"username": "attacker@example.com"},
"sourceIPs": ["203.0.113.42"],
"userAgent": "kubectl/v1.28.0",
"objectRef": {
"resource": "pods",
"subresource": "exec",
"namespace": "production",
"name": "webapp-pod",
"apiVersion": "v1"
},
"responseStatus": {"code": 101},
"requestObject": {
"command": ["cat", "/var/run/secrets/kubernetes.io/serviceaccount/token"],
"container": "webapp",
"stdin": false,
"stdout": true,
"tty": false
},
"requestURI": "/api/v1/namespaces/production/pods/webapp-pod/exec?command=cat&command=%2Fvar%2Frun%2Fsecrets%2Fkubernetes.io%2Fserviceaccount%2Ftoken&stdout=true",
"p_log_type": "Amazon.EKS.Audit",
"p_source_label": "eks-cluster"
}
- Name: AKS read serviceaccount directory
ExpectedResult: true
Log:
{
"p_log_type": "Azure.MonitorActivity",
"category": "kube-audit",
"operationName": "Microsoft.ContainerService/managedClusters/diagnosticLogs/Read",
"properties": {
"log": "{\"kind\":\"Event\",\"apiVersion\":\"audit.k8s.io/v1\",\"verb\":\"create\",\"user\":{\"username\":\"malicious-user@example.com\"},\"sourceIPs\":[\"1.2.3.4\"],\"objectRef\":{\"resource\":\"pods\",\"subresource\":\"exec\",\"namespace\":\"default\",\"name\":\"api-pod\"},\"responseStatus\":{\"code\":101},\"requestObject\":{\"command\":[\"ls\",\"/var/run/secrets/kubernetes.io/serviceaccount\"],\"container\":\"api\"}}"
},
"p_source_label": "aks-cluster"
}
- Name: GKE steal token with sh -c
ExpectedResult: true
Log:
{
"protoPayload": {
"authenticationInfo": {"principalEmail": "user@company.com"},
"authorizationInfo": [{
"granted": true,
"permission": "io.k8s.core.v1.pods.exec.create",
"resource": "core/v1/namespaces/production/pods/database/exec"
}],
"methodName": "io.k8s.core.v1.pods.exec.create",
"requestMetadata": {"callerIP": "8.8.8.8"},
"resourceName": "core/v1/namespaces/production/pods/database/exec",
"serviceName": "k8s.io",
"request": {
"command": ["sh", "-c", "cat /var/run/secrets/kubernetes.io/serviceaccount/token"],
"container": "postgres"
}
},
"resource": {
"type": "k8s_cluster",
"labels": {"project_id": "test-project"}
},
"p_log_type": "GCP.AuditLog",
"p_source_label": "gke-cluster"
}
- Name: EKS copy entire serviceaccount directory
ExpectedResult: true
Log:
{
"kind": "Event",
"verb": "create",
"user": {"username": "attacker@example.com"},
"objectRef": {
"resource": "pods",
"subresource": "exec",
"namespace": "default",
"name": "compromised-pod"
},
"responseStatus": {"code": 101},
"requestObject": {
"command": ["tar", "czf", "-", "/var/run/secrets/kubernetes.io/serviceaccount"]
},
"p_log_type": "Amazon.EKS.Audit",
"p_source_label": "eks-cluster"
}
- Name: Regular exec without token access
ExpectedResult: false
Log:
{
"kind": "Event",
"verb": "create",
"user": {"username": "admin@example.com"},
"objectRef": {
"resource": "pods",
"subresource": "exec",
"namespace": "default",
"name": "debug-pod"
},
"responseStatus": {"code": 101},
"requestObject": {
"command": ["/bin/sh"],
"stdin": true,
"tty": true
},
"p_log_type": "Amazon.EKS.Audit"
}
- Name: Regular user in system namespace (alert)
ExpectedResult: true
Log:
{
"kind": "Event",
"verb": "create",
"user": {"username": "admin@example.com"},
"objectRef": {
"resource": "pods",
"subresource": "exec",
"namespace": "kube-system",
"name": "system-pod"
},
"responseStatus": {"code": 101},
"requestObject": {
"command": ["cat", "/var/run/secrets/kubernetes.io/serviceaccount/token"]
},
"p_log_type": "Amazon.EKS.Audit"
}
- Name: System principal in regular namespace (alert)
ExpectedResult: true
Log:
{
"kind": "Event",
"verb": "create",
"user": {"username": "system:serviceaccount:kube-system:controller"},
"objectRef": {
"resource": "pods",
"subresource": "exec",
"namespace": "default",
"name": "app-pod"
},
"responseStatus": {"code": 101},
"requestObject": {
"command": ["cat", "/var/run/secrets/kubernetes.io/serviceaccount/token"]
},
"p_log_type": "Amazon.EKS.Audit"
}
- Name: System principal in system namespace (excluded)
ExpectedResult: false
Log:
{
"kind": "Event",
"verb": "create",
"user": {"username": "system:serviceaccount:kube-system:kube-proxy"},
"objectRef": {
"resource": "pods",
"subresource": "exec",
"namespace": "kube-system",
"name": "system-pod"
},
"responseStatus": {"code": 101},
"requestObject": {
"command": ["cat", "/var/run/secrets/kubernetes.io/serviceaccount/token"]
},
"p_log_type": "Amazon.EKS.Audit"
}
- Name: Failed exec (excluded)
ExpectedResult: false
Log:
{
"kind": "Event",
"verb": "create",
"user": {"username": "attacker@example.com"},
"objectRef": {
"resource": "pods",
"subresource": "exec",
"namespace": "production",
"name": "secure-pod"
},
"responseStatus": {"code": 403},
"requestObject": {
"command": ["cat", "/var/run/secrets/kubernetes.io/serviceaccount/token"]
},
"p_log_type": "Amazon.EKS.Audit"
}
- Name: EKS IRSA token theft
ExpectedResult: true
Log:
{
"kind": "Event",
"apiVersion": "audit.k8s.io/v1",
"verb": "create",
"user": {"username": "attacker@example.com"},
"sourceIPs": ["10.0.1.50"],
"objectRef": {
"resource": "pods",
"subresource": "exec",
"namespace": "production",
"name": "irsa-pod",
"apiVersion": "v1"
},
"responseStatus": {"code": 101},
"requestObject": {
"command": ["cat", "/var/run/secrets/eks.amazonaws.com/serviceaccount/token"],
"container": "app",
"stdout": true
},
"p_log_type": "Amazon.EKS.Audit",
"p_source_label": "eks-prod-cluster"
}
- Name: AKS workload identity token theft
ExpectedResult: true
Log:
{
"p_log_type": "Azure.MonitorActivity",
"category": "kube-audit",
"operationName": "Microsoft.ContainerService/managedClusters/diagnosticLogs/Read",
"properties": {
"log": "{\"kind\":\"Event\",\"apiVersion\":\"audit.k8s.io/v1\",\"verb\":\"create\",\"user\":{\"username\":\"attacker@company.com\"},\"sourceIPs\":[\"192.168.1.10\"],\"objectRef\":{\"resource\":\"pods\",\"subresource\":\"exec\",\"namespace\":\"production\",\"name\":\"workload-id-pod\"},\"responseStatus\":{\"code\":101},\"requestObject\":{\"command\":[\"cat\",\"/var/run/secrets/azure/tokens/azure-identity-token\"],\"container\":\"main\"}}"
},
"p_source_label": "aks-prod-cluster"
}
- Name: GKE workload identity token theft
ExpectedResult: true
Log:
{
"protoPayload": {
"authenticationInfo": {"principalEmail": "malicious@example.com"},
"authorizationInfo": [{
"granted": true,
"permission": "io.k8s.core.v1.pods.exec.create",
"resource": "core/v1/namespaces/default/pods/gke-pod/exec"
}],
"methodName": "io.k8s.core.v1.pods.exec.create",
"requestMetadata": {"callerIP": "35.1.2.3"},
"resourceName": "core/v1/namespaces/default/pods/gke-pod/exec",
"serviceName": "k8s.io",
"request": {
"command": ["cat", "/var/run/secrets/tokens/gcp-ksa/token"],
"container": "application"
}
},
"resource": {
"type": "k8s_cluster",
"labels": {"project_id": "production-project"}
},
"p_log_type": "GCP.AuditLog",
"p_source_label": "gke-production"
}
# ------ paired body: k8s_steal_serviceaccount_token.py ------
from urllib.parse import unquote
from panther_kubernetes_helpers import (
is_failed_request,
is_system_namespace,
is_system_principal,
k8s_alert_context,
)
# Paths related to service account tokens that attackers target
SERVICE_ACCOUNT_TOKEN_PATHS = {
# Standard Kubernetes service account token
"/var/run/secrets/kubernetes.io/serviceaccount/token",
"/var/run/secrets/kubernetes.io/serviceaccount",
# AWS EKS with IRSA (IAM Roles for Service Accounts)
"/var/run/secrets/eks.amazonaws.com/serviceaccount/token",
"/var/run/secrets/eks.amazonaws.com/serviceaccount",
# Azure AKS with Workload Identity
"/var/run/secrets/azure/tokens/azure-identity-token",
"/var/run/secrets/azure/tokens",
# GCP GKE with Workload Identity
"/var/run/secrets/tokens/gcp-ksa/token",
"/var/run/secrets/tokens/gcp-ksa",
# Partial path match for various token access patterns
"serviceaccount/token",
}
def rule(event):
verb = event.udm("verb")
resource = event.udm("resource")
subresource = event.udm("subresource")
namespace = event.udm("namespace")
username = event.udm("username")
response_status = event.udm("responseStatus")
# Only check exec subresource operations
if verb != "create" or resource != "pods" or subresource != "exec":
return False
# Skip failed requests
if is_failed_request(response_status):
return False
# Exclude system principals in system namespaces (legitimate operations)
# but alert on users stealing tokens from system namespaces (malicious)
if is_system_principal(username) and is_system_namespace(namespace):
return False
# Check command in requestObject
request_object = event.udm("requestObject") or {}
command = request_object.get("command", [])
command_str = " ".join(str(cmd) for cmd in command).lower()
# Check if command references service account token paths
if any(path.lower() in command_str for path in SERVICE_ACCOUNT_TOKEN_PATHS):
return True
# Check requestURI (may contain URL-encoded paths)
request_uri = event.udm("requestURI") or ""
# URL decode to handle encoded characters like %2F for /
decoded_uri = unquote(request_uri).lower()
if any(path.lower() in decoded_uri for path in SERVICE_ACCOUNT_TOKEN_PATHS):
return True
return False
def title(event):
username = event.udm("username") or "<UNKNOWN_USER>"
namespace = event.udm("namespace") or "<UNKNOWN_NAMESPACE>"
name = event.udm("name") or "<UNKNOWN_POD>"
return (
f"[{username}] attempted to steal service account token from pod " f"[{namespace}/{name}]"
)
def dedup(event):
username = event.udm("username") or "<UNKNOWN_USER>"
namespace = event.udm("namespace") or "<UNKNOWN_NAMESPACE>"
name = event.udm("name") or "<UNKNOWN_POD>"
return f"k8s_steal_token_{username}_{namespace}_{name}"
def alert_context(event):
request_object = event.udm("requestObject") or {}
command = request_object.get("command", [])
request_uri = event.udm("requestURI") or ""
return k8s_alert_context(
event,
extra_fields={
"pod_name": event.udm("name"),
"command": command,
"request_uri": request_uri,
"container": request_object.get("container"),
},
)