AnalysisType: rule
Filename: k8s_secret_enumeration.py
RuleID: "Kubernetes.BulkSecretAccess"
DisplayName: "Kubernetes Secret Enumeration by a User"
Status: Experimental
Enabled: false
Severity: Medium
DedupPeriodMinutes: 30
Threshold: 15
LogTypes:
- Amazon.EKS.Audit
- Azure.MonitorActivity
- GCP.AuditLog
Tags:
- Kubernetes
- Credential Access
Description: >
Detects when a single user accesses 15 or more distinct secrets within 30 minutes
using list, get, or watch verbs. This may indicate secret enumeration to enable
lateral or vertical movement and unauthorized access to critical resources.
The threshold should be tuned to your environment.
Reports:
MITRE ATT&CK:
- TA0006:T1552.007
Runbook: |
1. Query Amazon.EKS.Audit for all secret access events by the username and userAgent in the 30 minutes around this alert to identify the full list of secrets accessed and the verbs used
2. Determine if the user or service account has a legitimate reason to access this volume of secrets, and check if the responseStatus codes indicate successful reads or denied attempts
3. Search for other suspicious API activity by this username in the past 24 hours, including privilege escalation attempts, role or clusterrole binding changes, or unusual resource access patterns
Tests:
- Name: User Gets a Secret
ExpectedResult: true
Log:
kind: Event
apiVersion: audit.k8s.io/v1
stage: ResponseComplete
verb: get
user:
username: attacker@example.com
userAgent: kubectl/v1.28.0
objectRef:
resource: secrets
namespace: production
name: db-credentials
apiVersion: v1
responseStatus:
code: 200
p_log_type: Amazon.EKS.Audit
- Name: User Lists Secrets
ExpectedResult: true
Log:
kind: Event
apiVersion: audit.k8s.io/v1
stage: ResponseComplete
verb: list
user:
username: attacker@example.com
userAgent: kubectl/v1.28.0
objectRef:
resource: secrets
namespace: production
apiVersion: v1
responseStatus:
code: 200
p_log_type: Amazon.EKS.Audit
- Name: User Watches Secrets
ExpectedResult: true
Log:
kind: Event
apiVersion: audit.k8s.io/v1
stage: ResponseComplete
verb: watch
user:
username: attacker@example.com
userAgent: kubectl/v1.28.0
objectRef:
resource: secrets
namespace: production
apiVersion: v1
responseStatus:
code: 200
p_log_type: Amazon.EKS.Audit
- Name: User Gets Secret - Access Denied
ExpectedResult: true
Log:
kind: Event
apiVersion: audit.k8s.io/v1
stage: ResponseComplete
verb: get
user:
username: attacker@example.com
userAgent: kubectl/v1.28.0
objectRef:
resource: secrets
namespace: production
name: api-token
apiVersion: v1
responseStatus:
code: 403
p_log_type: Amazon.EKS.Audit
- Name: System Principal Excluded
ExpectedResult: false
Log:
kind: Event
apiVersion: audit.k8s.io/v1
stage: ResponseComplete
verb: list
user:
username: system:serviceaccount:kube-system:namespace-controller
userAgent: kube-controller-manager/v1.28.0
objectRef:
resource: secrets
apiVersion: v1
responseStatus:
code: 200
p_log_type: Amazon.EKS.Audit
- Name: Non-Secret Resource
ExpectedResult: false
Log:
kind: Event
apiVersion: audit.k8s.io/v1
stage: ResponseComplete
verb: get
user:
username: developer@example.com
userAgent: kubectl/v1.28.0
objectRef:
resource: configmaps
namespace: production
name: app-config
apiVersion: v1
responseStatus:
code: 200
p_log_type: Amazon.EKS.Audit
- Name: Write Verb Excluded
ExpectedResult: false
Log:
kind: Event
apiVersion: audit.k8s.io/v1
stage: ResponseComplete
verb: create
user:
username: developer@example.com
userAgent: kubectl/v1.28.0
objectRef:
resource: secrets
namespace: production
name: new-secret
apiVersion: v1
responseStatus:
code: 201
p_log_type: Amazon.EKS.Audit
# ------ paired body: k8s_secret_enumeration.py ------
from panther_kubernetes_helpers import is_failed_request, is_system_principal, k8s_alert_context
SECRET_VERBS = {"list", "get", "watch"}
def rule(event):
if event.udm("verb") not in SECRET_VERBS:
return False
if event.udm("resource") != "secrets":
return False
if is_system_principal(event.udm("username") or ""):
return False
return True
def title(event):
username = event.udm("username") or "<UNKNOWN_USER>"
return f"Kubernetes Secret Enumeration by [{username}]"
def dedup(event):
return event.udm("username") or "<UNKNOWN_USER>"
def unique(event):
secret_name = event.udm("name")
if secret_name:
return secret_name
# List/watch requests target secret collections and often omit objectRef.name.
verb = event.udm("verb")
if verb in ("list", "watch"):
namespace = event.udm("namespace")
if namespace:
return f"{verb}:{namespace}"
request_uri = event.udm("requestURI")
if request_uri:
return f"{verb}:{request_uri}"
return verb
return None
def severity(event):
if not is_failed_request(event.udm("responseStatus")):
return "HIGH"
return "DEFAULT"
def alert_context(event):
return k8s_alert_context(
event,
extra_fields={
"secret_name": event.udm("name"),
"verb": event.udm("verb"),
"user_agent": event.udm("userAgent"),
},
)