Kubernetes Secret Enumeration by a User


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.

Query · python

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"),
        },
    )

Analyst notes

  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
Raw source Kubernetes Secret Enumeration by a User · Panther Python
Esc
Published by panther-labs/panther-analysis ↗, licensed under Apache 2.0 ↗. Reproduced here unmodified.
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"),
        },
    )

Detection rules belong to the projects that publish them and remain under their own licenses. This site indexes and links to them; it claims no rights in them.