Detection rules › Panther

Kubernetes Secret Enumeration by a User

Status
Experimental
Severity
medium
Group by
username
Log types
Amazon.EKS.Audit, Azure.MonitorActivity, GCP.AuditLog
Tags
Kubernetes, Credential Access
Source
github.com/panther-labs/panther-analysis

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

TacticTechniques
Credential AccessT1552.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.

FieldKindExcluded values
usernamecontainsserviceaccount
usernamestarts_withsystem:
usernameinaksService, masterclient
usernameis_not_null(no value, null check)

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.

FieldKindValues
resourceeq
  • secrets
usernamecontains
  • serviceaccount
verbin
  • get
  • list
  • watch

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.

FieldSource
username
sourceIPs
userAgent
namespace
verb
resource
requestURI
responseStatus
clusterp_source_label