diff --git a/rules/cross-platform/credential_access_multi_could_secrets_via_api.toml b/rules/cross-platform/credential_access_multi_could_secrets_via_api.toml index e34fa771a..a8745d159 100644 --- a/rules/cross-platform/credential_access_multi_could_secrets_via_api.toml +++ b/rules/cross-platform/credential_access_multi_could_secrets_via_api.toml @@ -1,16 +1,17 @@ [metadata] creation_date = "2025/12/01" -integration = ["aws", "gcp", "azure"] +integration = ["aws", "gcp", "azure", "kubernetes"] maturity = "production" -updated_date = "2026/01/26" +updated_date = "2026/03/26" [rule] author = ["Elastic"] description = """ -This rule detects authenticated sessions accessing secret stores across multiple cloud providers from the same source -address within a short period of time. Adversaries with access to compromised credentials or session tokens may attempt -to retrieve secrets from services such as AWS Secrets Manager, Google Secret Manager, or Azure Key Vault in rapid -succession to expand their access or exfiltrate sensitive information. +This rule detects authenticated sessions accessing secret stores across multiple environments from the same source +address within a short period of time, including cloud providers (AWS, GCP, Azure) and Kubernetes clusters. +Adversaries with access to compromised credentials or session tokens may attempt to retrieve secrets from services such +as AWS Secrets Manager, Google Secret Manager, Azure Key Vault, or Kubernetes Secrets in rapid succession to expand +their access or exfiltrate sensitive information. """ from = "now-9m" interval = "5m" @@ -52,6 +53,11 @@ Unexpected cross-cloud secret retrieval is uncommon and typically indicates auto - Assess potential compromise scope - If compromise is suspected, enumerate other assets accessed by the same identity in the last 24 hours. - Look for lateral movement, privilege escalation, or abnormal API usage. +- Review Kubernetes activity + - Identify the Kubernetes user, service account, or workload performing the secret access. + - Determine whether the access originated from a pod, node, or external client. + - Validate whether the identity is expected to access secrets in the affected namespaces. + - Investigate whether the activity corresponds to application behavior or manual/API access. ### False positive analysis @@ -60,6 +66,8 @@ Unexpected cross-cloud secret retrieval is uncommon and typically indicates auto - Confirm that the identity is authorized to access secrets across multiple cloud services. - If activity is expected, consider adding exceptions that pair account identity, source IP, and expected user agent to reduce noise. +- Determine whether the source IP is associated with Kubernetes nodes, controllers, or internal workloads + that legitimately retrieve secrets alongside cloud provider integrations. ### Response and remediation @@ -72,6 +80,10 @@ Unexpected cross-cloud secret retrieval is uncommon and typically indicates auto - Reduce permissions to least privilege. - Review trust relationships, workload identities, and cross-cloud integrations. - Search for persistence mechanisms** such as newly created keys, roles, or service accounts. +- If Kubernetes access is involved: + - Rotate Kubernetes secrets and service account tokens. + - Review RBAC permissions and restrict secret access to least privilege. + - Inspect affected pods or nodes for compromise. - Improve monitoring and audit visibility** by ensuring logging is enabled across all cloud environments. - Determine root cause** (phishing, malware, token replay, exposed credential, etc.) and close the vector to prevent recurrence. """ @@ -102,6 +114,7 @@ tags = [ "Data Source: Azure Activity Logs", "Data Source: GCP", "Data Source: Google Cloud Platform", + "Data Source: Kubernetes", "Tactic: Credential Access", "Resources: Investigation Guide", ] @@ -109,31 +122,30 @@ timestamp_override = "event.ingested" type = "esql" query = ''' -FROM logs-azure.platformlogs-*, logs-aws.cloudtrail-*, logs-gcp.audit-* METADATA _id, _version, _index +FROM logs-azure.platformlogs-*, logs-aws.cloudtrail-*, logs-gcp.audit-*, logs-kubernetes.audit_logs-* METADATA _id, _version, _index | WHERE ( /* AWS Secrets Manager */ - (event.dataset == "aws.cloudtrail" AND event.provider == "secretsmanager.amazonaws.com" AND event.action == "GetSecretValue") OR + (event.dataset == "aws.cloudtrail" AND event.action == "GetSecretValue") OR // Azure Key Vault (platform logs) (event.dataset == "azure.platformlogs" AND event.action IN ("SecretGet", "KeyGet")) or /* Google Secret Manager */ (event.dataset IN ("googlecloud.audit", "gcp.audit") AND - event.action IN ("google.cloud.secretmanager.v1.SecretManagerService.AccessSecretVersion", "google.cloud.secretmanager.v1.SecretManagerService.GetSecretRequest")) + event.action IN ("google.cloud.secretmanager.v1.SecretManagerService.AccessSecretVersion", "google.cloud.secretmanager.v1.SecretManagerService.GetSecretRequest")) OR + + /* Kubernetes Secrets */ + (event.dataset == "kubernetes.audit_logs" AND kubernetes.audit.objectRef.resource == "secrets" AND kubernetes.audit.verb IN ("get", "list")) + ) AND source.ip IS NOT NULL -// Unified user identity (raw) -| EVAL Esql_priv.user_id = - COALESCE( - client.user.id, - aws.cloudtrail.user_identity.arn, - NULL - ) + // Cloud vendor label based on dataset | EVAL Esql.cloud_vendor = CASE( event.dataset == "aws.cloudtrail", "aws", event.dataset == "azure.platformlogs", "azure", event.dataset IN ("googlecloud.audit","gcp.audit"), "gcp", + event.dataset == "kubernetes.audit_logs", "k8s", "unknown" ) // Vendor+tenant label, e.g. aws:123456789012, azure:tenant, gcp:project @@ -141,8 +153,10 @@ FROM logs-azure.platformlogs-*, logs-aws.cloudtrail-*, logs-gcp.audit-* METADAT Esql.cloud_vendor == "aws", CONCAT("aws:", cloud.account.id), Esql.cloud_vendor == "azure", CONCAT("azure:", cloud.account.id), Esql.cloud_vendor == "gcp", CONCAT("gcp:", cloud.account.id), - NULL + Esql.cloud_vendor == "k8s", CONCAT("k8s:", orchestrator.cluster.name), + "unknown" ) +| WHERE Esql.cloud_vendor != "unknown" | STATS // Core counts Esql.events_count = COUNT(*), @@ -154,18 +168,15 @@ FROM logs-azure.platformlogs-*, logs-aws.cloudtrail-*, logs-gcp.audit-* METADAT Esql.cloud_vendor_values = VALUES(Esql.cloud_vendor), Esql.tenant_label_values = VALUES(Esql.tenant_label), // Hyperscaler-specific IDs - Esql.aws_account_id_values = VALUES(CASE(Esql.cloud_vendor == "aws", cloud.account.id, NULL)), - Esql.azure_tenant_id_values = VALUES(CASE(Esql.cloud_vendor == "azure", cloud.account.id, NULL)), - Esql.gcp_project_id_values = VALUES(CASE(Esql.cloud_vendor == "gcp", cloud.account.id, NULL)), + Esql.aws_account_id_values = VALUES(CASE(Esql.cloud_vendor == "aws", cloud.account.id, "unknown")), + Esql.azure_tenant_id_values = VALUES(CASE(Esql.cloud_vendor == "azure", cloud.account.id, "unknown")), + Esql.gcp_project_id_values = VALUES(CASE(Esql.cloud_vendor == "gcp", cloud.account.id, "unknown")), // Generic cloud metadata Esql.cloud_region_values = VALUES(cloud.region), - Esql.cloud_service_name_values = VALUES(cloud.service.name), - // Identity (privileged) - Esql_priv.user_values = VALUES(Esql_priv.user_id), - Esql_priv.client_user_id_values = VALUES(client.user.id), - Esql_priv.aws_user_identity_arn_values = VALUES(aws.cloudtrail.user_identity.arn), + Esql.k8s_namespace_values = VALUES(kubernetes.audit.objectRef.namespace), // Namespace values - Esql.data_stream_namespace_values = VALUES(data_stream.namespace) + Esql.data_stream_namespace_values = VALUES(data_stream.namespace), + Esql_priv.user_name_values = VALUES(user.name) BY source.ip // Require multi-vendor cred-access from same source IP | WHERE Esql.vendor_count_distinct >= 2