Detection rules › Elastic

Kubernetes Rapid Secret GET Activity Against Multiple Objects

Status
production
Severity
high
Time window
6m
Group by
kubernetes.audit.user.username, source.ip, user.name, user_agent.original
Author
Elastic
Source
github.com/elastic/detection-rules

This rule detects an unusual volume of Kubernetes API get requests against multiple distinct Secret objects from the same client fingerprint (user, source IP, and user agent) within a defined lookback window. This can indicate credential access or in-cluster reconnaissance, where a user or token is used to enumerate and retrieve sensitive data such as service account tokens, registry credentials, TLS material, or application configuration. Failed get requests are also included, as they may reveal RBAC boundaries, confirm the existence of targeted secrets, or reflect automated probing activity.

MITRE ATT&CK coverage

TacticTechniques
Credential AccessT1552.007 Unsecured Credentials: Container API

Event coverage

Rules detecting the same action

Other rules on this platform that filter on the same API call or operation.

Rule body elastic

[metadata]
creation_date = "2026/04/22"
integration = ["kubernetes"]
maturity = "production"
updated_date = "2026/05/15"

[rule]
author = ["Elastic"]
description = """
This rule detects an unusual volume of Kubernetes API get requests against multiple distinct Secret objects from the same client 
fingerprint (user, source IP, and user agent) within a defined lookback window. This can indicate credential access or in-cluster
reconnaissance, where a user or token is used to enumerate and retrieve sensitive data such as service account tokens, registry 
credentials, TLS material, or application configuration. Failed get requests are also included, as they may reveal RBAC boundaries, 
confirm the existence of targeted secrets, or reflect automated probing activity.
"""
from = "now-6m"
language = "esql"
license = "Elastic License v2"
name = "Kubernetes Rapid Secret GET Activity Against Multiple Objects"
note = """## Triage and analysis

### Investigating Kubernetes Rapid Secret GET Activity Against Multiple Objects

This rule surfaces clusters of `get` operations on the `secrets` API where the same identity and client path
(`user.name`, `source.ip`, `user_agent.original`) touch several different secret names within the rule lookback window.
**Allowed and denied** outcomes are included: successful reads may indicate harvesting; repeated **forbidden** or
**unauthorized** responses can still signal reconnaissance, RBAC probing, or scripted spray against secret names that
exist in the cluster.

### Investigation steps

- Inspect `Esql.outcome` for a mix of allow vs deny and whether failures cluster on sensitive namespaces.
- Map the identity to RBAC and namespace scope; review `Esql.secrets_names` and `Esql.namespaces` for high-value
  targets (tokens, registry credentials, TLS bundles, application secrets).
- Pivot on the same `source.ip` and user for follow-on API activity (exec, pod create, role changes, broad `list` on secrets).
- Validate against expected automation (CI, GitOps, backup, in-cluster controllers) before treating as malicious.

### False positives

- Startup, Helm, or controllers may legitimately touch many secrets in one window; tune by user, namespace, or IP
  allowlists when baselined.
"""
references = [
    "https://attack.mitre.org/techniques/T1552/007/",
]
risk_score = 73
rule_id = "b4c8e2a1-9f3d-4e7c-a2b1-0d5e6f7a8b9c"
severity = "high"
tags = [
    "Data Source: Kubernetes",
    "Domain: Kubernetes",
    "Use Case: Threat Detection",
    "Tactic: Credential Access",
    "Resources: Investigation Guide",
]
timestamp_override = "event.ingested"
type = "esql"
query = '''
from logs-kubernetes.audit_logs-* metadata _id, _index, _version
| where event.dataset == "kubernetes.audit_logs"
    and event.action == "get"
    and kubernetes.audit.objectRef.resource == "secrets"
    and source.ip is not null and user.name is not null 
    and not to_string(source.ip) in ("127.0.0.1", "::1") and 
    not user.name in ("system:kube-controller-manager", "system:kube-scheduler") and 
    not kubernetes.audit.objectRef.name like "sh.helm.release.*" and 
    not kubernetes.audit.user.username in ("system:serviceaccount:flux-system:kustomize-controller", "system:serviceaccount:flux-system:helm-controller", "system:serviceaccount:flux-system:source-controller", "system:serviceaccount:security:trivy-operator")
| stats
    Esql.unique_credentials = count_distinct(kubernetes.audit.objectRef.name),
    Esql.secrets_names = values(kubernetes.audit.objectRef.name),
    Esql.namespaces = values(kubernetes.audit.objectRef.namespace),
    Esql.outcome = values(`kubernetes.audit.annotations.authorization_k8s_io/decision`)
  by user.name, kubernetes.audit.user.username, source.ip, user_agent.original
| where Esql.unique_credentials >= 3
| KEEP user.name, kubernetes.audit.user.username, source.ip, user_agent.original, Esql.*
'''

[[rule.threat]]
framework = "MITRE ATT&CK"

[[rule.threat.technique]]
id = "T1552"
name = "Unsecured Credentials"
reference = "https://attack.mitre.org/techniques/T1552/"

[[rule.threat.technique.subtechnique]]
id = "T1552.007"
name = "Container API"
reference = "https://attack.mitre.org/techniques/T1552/007/"

[rule.threat.tactic]
id = "TA0006"
name = "Credential Access"
reference = "https://attack.mitre.org/tactics/TA0006/"

Stages and Predicates

Stage 1: from

from logs-kubernetes.audit_logs-* metadata _id, _index, _version

Stage 2: where

| where event.dataset == "kubernetes.audit_logs"
    and event.action == "get"
    and kubernetes.audit.objectRef.resource == "secrets"
    and source.ip is not null and user.name is not null 
    and not to_string(source.ip) in ("127.0.0.1", "::1") and 
    not user.name in ("system:kube-controller-manager", "system:kube-scheduler") and 
    not kubernetes.audit.objectRef.name like "sh.helm.release.*" and 
    not kubernetes.audit.user.username in ("system:serviceaccount:flux-system:kustomize-controller", "system:serviceaccount:flux-system:helm-controller", "system:serviceaccount:flux-system:source-controller", "system:serviceaccount:security:trivy-operator")

Stage 3: stats

| stats
    Esql.unique_credentials = count_distinct(kubernetes.audit.objectRef.name),
    Esql.secrets_names = values(kubernetes.audit.objectRef.name),
    Esql.namespaces = values(kubernetes.audit.objectRef.namespace),
    Esql.outcome = values(`kubernetes.audit.annotations.authorization_k8s_io/decision`)
  by user.name, kubernetes.audit.user.username, source.ip, user_agent.original

Stage 4: where

| where Esql.unique_credentials >= 3

Stage 5: keep

| KEEP user.name, kubernetes.audit.user.username, source.ip, user_agent.original, Esql.*

Exclusions

Top-level NOT(...) conjuncts: predicates this rule actively suppresses.

FieldKindExcluded values
kubernetes.audit.objectRef.namestarts_withsh.helm.release.
kubernetes.audit.user.usernameinsystem:serviceaccount:flux-system:helm-controller, system:serviceaccount:flux-system:kustomize-controller, system:serviceaccount:flux-system:source-controller, system:serviceaccount:security:trivy-operator
user.nameinsystem:kube-controller-manager, system:kube-scheduler

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
Esql.unique_credentialsge
  • 3
event.actioneq
  • get
event.dataseteq
  • kubernetes.audit_logs
kubernetes.audit.objectRef.resourceeq
  • secrets
source.ipis_not_null
  • (no value, null check)
user.nameis_not_null
  • (no value, null check)

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
user.nameKEEP user.name
kubernetes.audit.user.usernameKEEP kubernetes.audit.user.username
source.ipKEEP source.ip
user_agent.originalKEEP user_agent.original
Esql.*KEEP Esql.*