Detection rules › Panther
Kubernetes Secret Enumeration by a User
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.
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Credential Access | T1552.007 Unsecured Credentials: Container API |
Rule body yaml
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
Detection logic
Condition
verb in ["list", "get", "watch"]
resource eq "secrets"
not (username is_not_null and (username in ["masterclient", "aksService"] or (username starts_with "system:" and username not contains "serviceaccount")))
This rule also runs imperative logic the parser cannot express as a filter; the conditions above are the structured part it could extract.
Exclusions
Top-level NOT(...) conjuncts: predicates this rule actively suppresses.
| Field | Kind | Excluded values |
|---|---|---|
username | contains | serviceaccount |
username | starts_with | system: |
username | in | aksService, masterclient |
username | is_not_null |
Indicators
Each row is a field, operator, and value that the rule matches. The corpus column counts how many other rules in the catalog look for the same combination: high numbers point to widely-used, community-vetted indicators. Blank or 1 shows that the indicator is specific to this rule.
Output fields
Fields the rule emits when it matches. Chronicle authors list these in the outcome block; they appear on the detection and $risk_score drives alerting. Sentinel / Defender XDR rules build them up through project / summarize / extend stages. Sentinel maps these into alert fields via entityMappings and customDetails; Defender XDR custom detections surface them as alert fields directly.
| Field | Source |
|---|---|
username | |
sourceIPs | |
userAgent | |
namespace | |
verb | |
resource | |
requestURI | |
responseStatus | |
cluster | p_source_label |