[New Rule] Azure Arc Kubernetes Cluster Connect Abuse (#5824)
* [New Rule] Azure Arc Kubernetes Cluster Connect Abuse Fixes #5823 * rename, adjusted query * adding KEEP * * adjusting maturity * added to non-ecs schema * updating rule * addressing unit test failures * adjustments to logic, mitre mappings, unit test failures, etc. * Update rules/integrations/azure/initial_access_azure_arc_cluster_credential_access_unusual_source.toml Co-authored-by: Mika Ayenson, PhD <Mikaayenson@users.noreply.github.com> --------- Co-authored-by: Mika Ayenson, PhD <Mikaayenson@users.noreply.github.com>
This commit is contained in:
@@ -223,7 +223,8 @@
|
||||
"azure.activitylogs.properties.appDisplayName": "keyword",
|
||||
"azure.activitylogs.properties.requestbody.properties.roleDefinitionId": "keyword",
|
||||
"azure.activitylogs.properties.responseBody": "keyword",
|
||||
"azure.activitylogs.properties.status_code": "keyword"
|
||||
"azure.activitylogs.properties.status_code": "keyword",
|
||||
"azure.activitylogs.identity.claims.appid": "keyword"
|
||||
},
|
||||
"logs-azure.graphactivitylogs-*": {
|
||||
"azure.graphactivitylogs.properties.c_idtyp": "keyword",
|
||||
|
||||
+151
@@ -0,0 +1,151 @@
|
||||
[metadata]
|
||||
creation_date = "2026/03/10"
|
||||
integration = ["azure"]
|
||||
maturity = "production"
|
||||
updated_date = "2026/03/10"
|
||||
|
||||
[rule]
|
||||
author = ["Elastic"]
|
||||
description = """
|
||||
Detects when a service principal authenticates to Microsoft Entra ID and then lists credentials for an Azure
|
||||
Arc-connected Kubernetes cluster within a short time window. The `listClusterUserCredential` action retrieves tokens
|
||||
that enable kubectl access through the Arc Cluster Connect proxy. This sequence (service principal sign-in followed by
|
||||
Arc credential retrieval), represents the exact attack chain used by adversaries with stolen service principal secrets
|
||||
to establish a proxy tunnel into Kubernetes clusters. Service principals that authenticate externally (as opposed to
|
||||
managed identities) and immediately access Arc cluster credentials warrant investigation, particularly when the sign-in
|
||||
originates from an unexpected location or ASN.
|
||||
"""
|
||||
false_positives = [
|
||||
"""
|
||||
CI/CD pipelines that authenticate as a service principal and then access Arc clusters as part of deployment
|
||||
workflows will trigger this rule. Identify and exclude known automation service principal app IDs.
|
||||
""",
|
||||
"""
|
||||
Administrators using service principal credentials to manage Arc-connected clusters during maintenance windows may
|
||||
trigger this rule. Correlate with change management records.
|
||||
""",
|
||||
]
|
||||
from = "now-30m"
|
||||
index = ["logs-azure.signinlogs-*", "logs-azure.activitylogs-*"]
|
||||
interval = "15m"
|
||||
language = "eql"
|
||||
license = "Elastic License v2"
|
||||
name = "Azure Service Principal Sign-In Followed by Arc Cluster Credential Access"
|
||||
note = """## Triage and analysis
|
||||
|
||||
### Investigating Azure Service Principal Sign-In Followed by Arc Cluster Credential Access
|
||||
|
||||
This rule detects the complete attack entry point for Arc-proxied Kubernetes attacks: a service principal authenticates
|
||||
to Azure AD, then immediately retrieves Arc cluster credentials. This is the prerequisite sequence before any
|
||||
Kubernetes-level activity can occur through the Arc proxy.
|
||||
|
||||
### Possible investigation steps
|
||||
|
||||
- Identify the service principal using the `app_id` from the sign-in event and resolve it in Azure AD — is this a
|
||||
known application?
|
||||
- Check the sign-in source IP and geolocation — does it match expected infrastructure locations for this SP?
|
||||
- Review when the SP credentials were last rotated — stale credentials are more likely compromised.
|
||||
- Check the ASN of the sign-in source — is it from a known cloud provider, corporate network, or unexpected consumer ISP?
|
||||
- Examine Azure Activity Logs after the credential listing for any Arc-proxied operations (secret/configmap CRUD).
|
||||
- Correlate with Kubernetes audit logs for operations by the Arc proxy service account
|
||||
(`system:serviceaccount:azure-arc:azure-arc-kube-aad-proxy-sa`) in the same time window.
|
||||
- Review Azure AD Audit Logs for recent changes to this SP (new credentials, federated identities, owner changes).
|
||||
|
||||
### Response and remediation
|
||||
|
||||
- Immediately rotate the service principal credentials (secrets and certificates).
|
||||
- Revoke active sessions and tokens for the SP.
|
||||
- Review and remove any unauthorized Azure role assignments on Arc-connected clusters.
|
||||
- Check Kubernetes audit logs for any operations performed through the Arc proxy after credential access.
|
||||
- Rotate any Kubernetes secrets that may have been accessed through the proxy tunnel.
|
||||
- Enable conditional access policies to restrict service principal authentication by location if supported.
|
||||
"""
|
||||
references = [
|
||||
"https://learn.microsoft.com/en-us/azure/azure-arc/kubernetes/cluster-connect",
|
||||
"https://learn.microsoft.com/en-us/cli/azure/connectedk8s#az-connectedk8s-proxy",
|
||||
"https://learn.microsoft.com/en-us/entra/identity/monitoring-health/concept-sign-ins",
|
||||
"https://www.ibm.com/think/x-force/identifying-abusing-azure-arc-for-hybrid-escalation-persistence",
|
||||
"https://www.microsoft.com/en-us/security/blog/2025/08/27/storm-0501s-evolving-techniques-lead-to-cloud-based-ransomware/",
|
||||
]
|
||||
risk_score = 47
|
||||
rule_id = "dacfbecd-7927-46a7-a8ba-feb65a2e990d"
|
||||
severity = "medium"
|
||||
tags = [
|
||||
"Domain: Cloud",
|
||||
"Domain: Identity",
|
||||
"Data Source: Azure",
|
||||
"Data Source: Azure Arc",
|
||||
"Data Source: Microsoft Entra ID",
|
||||
"Data Source: Microsoft Entra ID Sign-In Logs",
|
||||
"Use Case: Threat Detection",
|
||||
"Tactic: Credential Access",
|
||||
"Tactic: Initial Access",
|
||||
"Resources: Investigation Guide",
|
||||
]
|
||||
timestamp_override = "event.ingested"
|
||||
type = "eql"
|
||||
|
||||
query = '''
|
||||
sequence with maxspan=30m
|
||||
[authentication where event.dataset == "azure.signinlogs"
|
||||
and azure.signinlogs.category == "ServicePrincipalSignInLogs"
|
||||
and azure.signinlogs.properties.status.error_code == 0
|
||||
] by azure.signinlogs.properties.app_id
|
||||
[any where event.dataset == "azure.activitylogs"
|
||||
and azure.activitylogs.operation_name : "MICROSOFT.KUBERNETES/CONNECTEDCLUSTERS/LISTCLUSTERUSERCREDENTIAL/ACTION"
|
||||
and event.outcome : ("Success", "success")
|
||||
] by azure.activitylogs.identity.claims.appid
|
||||
'''
|
||||
|
||||
|
||||
[[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/"
|
||||
[[rule.threat]]
|
||||
framework = "MITRE ATT&CK"
|
||||
[[rule.threat.technique]]
|
||||
id = "T1078"
|
||||
name = "Valid Accounts"
|
||||
reference = "https://attack.mitre.org/techniques/T1078/"
|
||||
[[rule.threat.technique.subtechnique]]
|
||||
id = "T1078.004"
|
||||
name = "Cloud Accounts"
|
||||
reference = "https://attack.mitre.org/techniques/T1078/004/"
|
||||
|
||||
|
||||
|
||||
[rule.threat.tactic]
|
||||
id = "TA0001"
|
||||
name = "Initial Access"
|
||||
reference = "https://attack.mitre.org/tactics/TA0001/"
|
||||
|
||||
[rule.investigation_fields]
|
||||
field_names = [
|
||||
"@timestamp",
|
||||
"azure.signinlogs.properties.app_id",
|
||||
"azure.signinlogs.properties.app_display_name",
|
||||
"azure.signinlogs.properties.service_principal_name",
|
||||
"azure.signinlogs.category",
|
||||
"azure.activitylogs.operation_name",
|
||||
"azure.activitylogs.identity.claims.appid",
|
||||
"azure.resource.id",
|
||||
"source.ip",
|
||||
"source.geo.country_name",
|
||||
"source.geo.city_name",
|
||||
"source.as.organization.name",
|
||||
]
|
||||
|
||||
+146
@@ -0,0 +1,146 @@
|
||||
[metadata]
|
||||
creation_date = "2026/03/10"
|
||||
integration = ["azure"]
|
||||
maturity = "production"
|
||||
updated_date = "2026/03/10"
|
||||
|
||||
[rule]
|
||||
author = ["Elastic"]
|
||||
description = """
|
||||
Detects when a service principal or user performs an Azure Arc cluster credential listing operation from a source IP not
|
||||
previously associated with that identity. The `listClusterUserCredential` action retrieves credentials for the Arc
|
||||
Cluster Connect proxy, enabling kubectl access through the Azure ARM API. An adversary using stolen service principal
|
||||
credentials will typically call this operation from infrastructure not previously seen for that SP. By tracking the
|
||||
combination of caller identity and source IP, this rule avoids false positives from backend services and CI/CD pipelines
|
||||
that rotate IPs but maintain consistent identity-to-IP patterns over time.
|
||||
"""
|
||||
false_positives = [
|
||||
"""
|
||||
A service principal used by a CI/CD pipeline may trigger this rule when the pipeline runs from a new IP range for
|
||||
the first time (e.g., migrating to a new runner pool). The 7-day history window will learn the new IPs after the
|
||||
first occurrence.
|
||||
""",
|
||||
"""
|
||||
Administrators accessing Arc clusters from a new VPN endpoint or travel location. Validate the caller identity
|
||||
matches an expected user and correlate with known travel or access patterns.
|
||||
""",
|
||||
]
|
||||
from = "now-9m"
|
||||
index = ["logs-azure.activitylogs-*"]
|
||||
language = "kuery"
|
||||
license = "Elastic License v2"
|
||||
name = "Azure Arc Cluster Credential Access by Identity from Unusual Source"
|
||||
note = """## Triage and analysis
|
||||
|
||||
### Investigating Azure Arc Cluster Credential Access by Identity from Unusual Source
|
||||
|
||||
The `listClusterUserCredential` operation on an Azure Arc-connected cluster returns credentials that allow the caller
|
||||
to establish a proxy tunnel via `az connectedk8s proxy`. This proxy routes kubectl commands through the Azure ARM API,
|
||||
enabling Kubernetes access without direct network connectivity to the cluster API server.
|
||||
|
||||
### Possible investigation steps
|
||||
|
||||
- Identify the caller service principal using `azure.activitylogs.identity.claims.appid` and cross-reference with
|
||||
Azure AD to determine if this is a known application.
|
||||
- Check the source IP and geolocation — is this from a country or ASN where your organization operates?
|
||||
- Correlate with Azure Sign-In Logs around the same time to see the full authentication chain (SP login followed by
|
||||
credential listing).
|
||||
- Verify the Azure role used — the `Azure Arc Enabled Kubernetes Cluster User Role` is required for this operation.
|
||||
Was this role recently assigned?
|
||||
- Check if subsequent Arc-proxied operations (secret/configmap CRUD) occurred after the credential access.
|
||||
- Review the service principal creation date in Azure AD — recently created SPs are more suspicious.
|
||||
|
||||
### Response and remediation
|
||||
|
||||
- If the source IP is from an unexpected country or the service principal is not recognized, treat as potential
|
||||
credential compromise.
|
||||
- Revoke the service principal credentials and remove Arc RBAC role assignments.
|
||||
- Review Kubernetes audit logs for any operations performed through the Arc proxy after credential access.
|
||||
- Rotate any Kubernetes secrets that may have been accessed.
|
||||
"""
|
||||
references = [
|
||||
"https://learn.microsoft.com/en-us/azure/azure-arc/kubernetes/cluster-connect",
|
||||
"https://learn.microsoft.com/en-us/cli/azure/connectedk8s#az-connectedk8s-proxy",
|
||||
"https://www.ibm.com/think/x-force/identifying-abusing-azure-arc-for-hybrid-escalation-persistence",
|
||||
"https://nvd.nist.gov/vuln/detail/cve-2022-37968",
|
||||
]
|
||||
risk_score = 47
|
||||
rule_id = "022c37cd-5a4f-422b-8227-b136b7a23180"
|
||||
severity = "medium"
|
||||
tags = [
|
||||
"Domain: Cloud",
|
||||
"Data Source: Azure",
|
||||
"Data Source: Azure Arc",
|
||||
"Data Source: Azure Activity Logs",
|
||||
"Use Case: Threat Detection",
|
||||
"Tactic: Initial Access",
|
||||
"Tactic: Credential Access",
|
||||
"Resources: Investigation Guide",
|
||||
]
|
||||
timestamp_override = "event.ingested"
|
||||
type = "new_terms"
|
||||
|
||||
query = '''
|
||||
event.dataset: "azure.activitylogs"
|
||||
and azure.activitylogs.operation_name: "MICROSOFT.KUBERNETES/CONNECTEDCLUSTERS/LISTCLUSTERUSERCREDENTIAL/ACTION"
|
||||
and event.outcome: (Success or success)
|
||||
'''
|
||||
|
||||
|
||||
[[rule.threat]]
|
||||
framework = "MITRE ATT&CK"
|
||||
[[rule.threat.technique]]
|
||||
id = "T1078"
|
||||
name = "Valid Accounts"
|
||||
reference = "https://attack.mitre.org/techniques/T1078/"
|
||||
[[rule.threat.technique.subtechnique]]
|
||||
id = "T1078.004"
|
||||
name = "Cloud Accounts"
|
||||
reference = "https://attack.mitre.org/techniques/T1078/004/"
|
||||
|
||||
|
||||
|
||||
[rule.threat.tactic]
|
||||
id = "TA0001"
|
||||
name = "Initial Access"
|
||||
reference = "https://attack.mitre.org/tactics/TA0001/"
|
||||
[[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/"
|
||||
|
||||
[rule.investigation_fields]
|
||||
field_names = [
|
||||
"@timestamp",
|
||||
"azure.activitylogs.operation_name",
|
||||
"azure.activitylogs.identity.claims.appid",
|
||||
"azure.activitylogs.identity.authorization.evidence.role",
|
||||
"azure.activitylogs.identity.authorization.evidence.principalType",
|
||||
"azure.resource.id",
|
||||
"source.ip",
|
||||
"source.geo.country_name",
|
||||
"source.geo.city_name",
|
||||
"source.as.organization.name",
|
||||
]
|
||||
|
||||
[rule.new_terms]
|
||||
field = "new_terms_fields"
|
||||
value = ["azure.activitylogs.identity.claims.appid", "source.ip"]
|
||||
[[rule.new_terms.history_window_start]]
|
||||
field = "history_window_start"
|
||||
value = "now-7d"
|
||||
|
||||
|
||||
+164
@@ -0,0 +1,164 @@
|
||||
[metadata]
|
||||
creation_date = "2026/03/10"
|
||||
integration = ["azure"]
|
||||
maturity = "production"
|
||||
updated_date = "2026/03/10"
|
||||
|
||||
[rule]
|
||||
author = ["Elastic"]
|
||||
description = """
|
||||
Detects when an Azure service principal authenticates from multiple countries within a short time window, which may
|
||||
indicate stolen credentials being used from different geographic locations. Service principals typically authenticate
|
||||
from consistent locations tied to their deployment infrastructure. Authentication from multiple countries in a brief
|
||||
period suggests credential compromise, particularly when the source countries do not align with the organization's
|
||||
expected operating regions. This pattern has been observed in attacks using stolen CI/CD credentials, phished service
|
||||
principal secrets, and compromised automation accounts.
|
||||
"""
|
||||
false_positives = [
|
||||
"""
|
||||
Service principals used by globally distributed CI/CD systems (e.g., GitHub Actions runners in multiple regions) may
|
||||
legitimately authenticate from different countries. Baseline the expected geographic distribution for each service
|
||||
principal.
|
||||
""",
|
||||
"""
|
||||
VPN or proxy usage by administrators managing service principals from different locations may produce multi-country
|
||||
sign-in patterns. Correlate with the administrator's known travel or access patterns.
|
||||
""",
|
||||
]
|
||||
from = "now-8h"
|
||||
interval = "1h"
|
||||
language = "esql"
|
||||
license = "Elastic License v2"
|
||||
name = "Azure Service Principal Authentication from Multiple Countries"
|
||||
note = """## Triage and analysis
|
||||
|
||||
### Investigating Azure Service Principal Authentication from Multiple Countries
|
||||
|
||||
Service principals are non-interactive identities used for automation and application access. Unlike user accounts,
|
||||
they rarely change geographic location. Authentication from multiple countries in a short window is a strong indicator
|
||||
of credential compromise.
|
||||
|
||||
### Possible investigation steps
|
||||
|
||||
- Identify the service principal using the `app_id` and `app_display_name` from the alert.
|
||||
- Review the list of countries and source IPs — do they match known infrastructure locations?
|
||||
- Check when the service principal credentials were last rotated — stale credentials are more likely compromised.
|
||||
- Investigate what resources were accessed after authentication using Azure Activity Logs and Graph Activity Logs.
|
||||
- Correlate with Azure AD Audit Logs for recent changes to the service principal (new credentials, federated
|
||||
identities, owner changes).
|
||||
- Check if the service principal has Azure Arc or Kubernetes-related role assignments, which could indicate
|
||||
targeting of cluster resources.
|
||||
|
||||
### False positive analysis
|
||||
- If the service principal is used by a CI/CD pipeline, check if the different countries align with known runner locations. Baseline the expected geographic distribution for that SP.
|
||||
- If administrators manage the SP, correlate with known travel patterns or VPN usage that could explain multi-country access.
|
||||
|
||||
### Response and remediation
|
||||
|
||||
- Immediately rotate the service principal credentials (secrets and certificates).
|
||||
- Revoke active sessions and tokens.
|
||||
- Review and remove any unauthorized role assignments.
|
||||
- Audit resources accessed from the suspicious locations.
|
||||
- Enable conditional access policies to restrict service principal authentication by location if supported.
|
||||
"""
|
||||
references = [
|
||||
"https://learn.microsoft.com/en-us/entra/identity/monitoring-health/concept-sign-ins",
|
||||
"https://learn.microsoft.com/en-us/entra/identity/conditional-access/workload-identities",
|
||||
"https://www.microsoft.com/en-us/security/blog/2025/08/27/storm-0501s-evolving-techniques-lead-to-cloud-based-ransomware/",
|
||||
"https://www.wiz.io/blog/lateral-movement-risks-in-the-cloud-and-how-to-prevent-them-part-3-from-compromis",
|
||||
]
|
||||
risk_score = 73
|
||||
rule_id = "db97a2aa-3ba5-4fa5-b8b9-bf42284edb5f"
|
||||
severity = "high"
|
||||
tags = [
|
||||
"Domain: Cloud",
|
||||
"Domain: Identity",
|
||||
"Data Source: Azure",
|
||||
"Data Source: Microsoft Entra ID",
|
||||
"Data Source: Microsoft Entra ID Sign-In Logs",
|
||||
"Use Case: Identity and Access Audit",
|
||||
"Use Case: Threat Detection",
|
||||
"Tactic: Initial Access",
|
||||
"Resources: Investigation Guide",
|
||||
]
|
||||
timestamp_override = "event.ingested"
|
||||
type = "esql"
|
||||
|
||||
query = '''
|
||||
FROM logs-azure.signinlogs-* metadata _id, _index
|
||||
| WHERE event.dataset == "azure.signinlogs"
|
||||
AND azure.signinlogs.category == "ServicePrincipalSignInLogs"
|
||||
AND azure.signinlogs.properties.status.error_code == 0
|
||||
AND source.geo.country_iso_code IS NOT NULL
|
||||
AND azure.signinlogs.properties.service_principal_id IS NOT NULL
|
||||
AND NOT azure.signinlogs.properties.app_owner_tenant_id IN (
|
||||
"f8cdef31-a31e-4b4a-93e4-5f571e91255a",
|
||||
"72f988bf-86f1-41af-91ab-2d7cd011db47"
|
||||
)
|
||||
|
||||
| EVAL
|
||||
Esql.source_ip_string = TO_STRING(source.ip),
|
||||
Esql.source_ip_country_pair = CONCAT(Esql.source_ip_string, " - ", source.geo.country_name)
|
||||
|
||||
| STATS
|
||||
Esql.source_geo_country_iso_code_count_distinct = COUNT_DISTINCT(source.geo.country_iso_code),
|
||||
Esql.source_geo_country_name_values = VALUES(source.geo.country_name),
|
||||
Esql.source_geo_city_name_values = VALUES(source.geo.city_name),
|
||||
Esql.source_ip_values = VALUES(source.ip),
|
||||
Esql.source_ip_country_pair_values = VALUES(Esql.source_ip_country_pair),
|
||||
Esql.source_network_org_name_values = VALUES(`source.as.organization.name`),
|
||||
Esql.resource_display_name_values = VALUES(azure.signinlogs.properties.resource_display_name),
|
||||
Esql.app_id_values = VALUES(azure.signinlogs.properties.app_id),
|
||||
Esql.app_owner_tenant_id_values = VALUES(azure.signinlogs.properties.app_owner_tenant_id),
|
||||
Esql.source_ip_count_distinct = COUNT_DISTINCT(source.ip),
|
||||
Esql.source_geo_city_name_count_distinct = COUNT_DISTINCT(source.geo.city_name),
|
||||
Esql.source_network_org_name_count_distinct = COUNT_DISTINCT(`source.as.organization.name`),
|
||||
Esql.timestamp_first_seen = MIN(@timestamp),
|
||||
Esql.timestamp_last_seen = MAX(@timestamp),
|
||||
Esql.event_count = COUNT(*)
|
||||
BY azure.signinlogs.properties.service_principal_id, azure.signinlogs.properties.app_display_name
|
||||
|
||||
| WHERE Esql.source_geo_country_iso_code_count_distinct >= 2
|
||||
| KEEP *
|
||||
'''
|
||||
|
||||
|
||||
[[rule.threat]]
|
||||
framework = "MITRE ATT&CK"
|
||||
[[rule.threat.technique]]
|
||||
id = "T1078"
|
||||
name = "Valid Accounts"
|
||||
reference = "https://attack.mitre.org/techniques/T1078/"
|
||||
[[rule.threat.technique.subtechnique]]
|
||||
id = "T1078.004"
|
||||
name = "Cloud Accounts"
|
||||
reference = "https://attack.mitre.org/techniques/T1078/004/"
|
||||
|
||||
|
||||
|
||||
[rule.threat.tactic]
|
||||
id = "TA0001"
|
||||
name = "Initial Access"
|
||||
reference = "https://attack.mitre.org/tactics/TA0001/"
|
||||
|
||||
[rule.investigation_fields]
|
||||
field_names = [
|
||||
"azure.signinlogs.properties.service_principal_id",
|
||||
"azure.signinlogs.properties.app_display_name",
|
||||
"Esql.source_geo_country_iso_code_count_distinct",
|
||||
"Esql.source_geo_country_name_values",
|
||||
"Esql.source_geo_city_name_values",
|
||||
"Esql.source_ip_values",
|
||||
"Esql.source_ip_country_pair_values",
|
||||
"Esql.source_network_org_name_values",
|
||||
"Esql.resource_display_name_values",
|
||||
"Esql.app_id_values",
|
||||
"Esql.app_owner_tenant_id_values",
|
||||
"Esql.source_ip_count_distinct",
|
||||
"Esql.source_geo_city_name_count_distinct",
|
||||
"Esql.source_network_org_name_count_distinct",
|
||||
"Esql.timestamp_first_seen",
|
||||
"Esql.timestamp_last_seen",
|
||||
"Esql.event_count",
|
||||
]
|
||||
|
||||
+141
@@ -0,0 +1,141 @@
|
||||
[metadata]
|
||||
creation_date = "2026/03/10"
|
||||
integration = ["kubernetes"]
|
||||
maturity = "production"
|
||||
updated_date = "2026/03/10"
|
||||
|
||||
[rule]
|
||||
author = ["Elastic"]
|
||||
description = """
|
||||
Detects when secrets or configmaps are accessed, created, modified, or deleted in a Kubernetes cluster by the Azure Arc
|
||||
AAD proxy service account. When operations are routed through the Azure Arc Cluster Connect proxy, the Kubernetes audit
|
||||
log records the acting user as `system:serviceaccount:azure-arc:azure-arc-kube-aad-proxy-sa` with the actual caller
|
||||
identity in the `impersonatedUser` field. This pattern indicates that someone is accessing the cluster through the Azure
|
||||
ARM API rather than directly via kubectl against the API server. While legitimate for Arc-managed workflows, adversaries
|
||||
with stolen service principal credentials can abuse Arc Cluster Connect to read, exfiltrate, or modify secrets and
|
||||
configmaps while appearing as the Arc proxy service account in K8s audit logs.
|
||||
"""
|
||||
false_positives = [
|
||||
"""
|
||||
Azure Arc system components may create or update secrets and configmaps in the azure-arc and azure-arc-release
|
||||
namespaces during normal cluster management. Filter by namespace to exclude these.
|
||||
""",
|
||||
"""
|
||||
Helm operations managed through Arc may create release secrets (prefixed with sh.helm.release.v1). These are normal
|
||||
Arc lifecycle operations.
|
||||
""",
|
||||
]
|
||||
from = "now-5d"
|
||||
interval = "9m"
|
||||
language = "esql"
|
||||
license = "Elastic License v2"
|
||||
name = "Kubernetes Secret or ConfigMap Access via Azure Arc Proxy"
|
||||
note = """## Triage and analysis
|
||||
|
||||
### Investigating Kubernetes Secret or ConfigMap Access via Azure Arc Proxy
|
||||
|
||||
When Kubernetes operations are performed through Azure Arc Cluster Connect, the K8s audit log shows the Arc AAD proxy
|
||||
service account as the authenticated user, with the actual Azure AD identity in the `impersonatedUser` field. This
|
||||
rule detects non-system secret and configmap access — including reads, writes, and deletions — routed through this
|
||||
proxy path. Read operations (`get`, `list`) are particularly important to detect as they represent the most common
|
||||
adversary action: exfiltrating secrets without leaving obvious modification traces.
|
||||
|
||||
### Possible investigation steps
|
||||
|
||||
- Check the `kubernetes.audit.impersonatedUser.username` field — this contains the Azure AD object ID of the actual
|
||||
caller. Cross-reference with Azure AD to identify the service principal or user.
|
||||
- Review the `kubernetes.audit.impersonatedUser.extra.oid` field for the Azure AD object ID.
|
||||
- Examine the namespace — operations in `default` or application namespaces are more suspicious than `azure-arc` or
|
||||
`kube-system`.
|
||||
- Check the `kubernetes.audit.objectRef.name` — look for suspicious secret/configmap names that don't match known
|
||||
application resources.
|
||||
- Correlate with Azure Activity Logs for the same time window to find the `LISTCLUSTERUSERCREDENTIAL` operation that
|
||||
initiated the Arc proxy session.
|
||||
- Review Azure Sign-In Logs for the impersonated identity's authentication source IP and geolocation.
|
||||
|
||||
### Response and remediation
|
||||
|
||||
- If the impersonated identity is not recognized, revoke its Azure AD credentials immediately.
|
||||
- Remove the ClusterRoleBinding or RoleBinding that grants the identity access to secrets/configmaps.
|
||||
- Rotate any Kubernetes secrets that may have been read or exfiltrated.
|
||||
- Review the Arc connection and consider disconnecting it if compromised.
|
||||
"""
|
||||
references = [
|
||||
"https://learn.microsoft.com/en-us/azure/azure-arc/kubernetes/cluster-connect",
|
||||
"https://microsoft.github.io/Threat-Matrix-for-Kubernetes/",
|
||||
"https://www.ibm.com/think/x-force/identifying-abusing-azure-arc-for-hybrid-escalation-persistence",
|
||||
"https://cloud.google.com/blog/topics/threat-intelligence/escalating-privileges-azure-kubernetes-services",
|
||||
"https://www.wiz.io/blog/lateral-movement-risks-in-the-cloud-and-how-to-prevent-them-part-3-from-compromis",
|
||||
]
|
||||
risk_score = 47
|
||||
rule_id = "220d92c6-479d-4a49-9cc0-3a29756dad0c"
|
||||
severity = "medium"
|
||||
tags = [
|
||||
"Data Source: Kubernetes",
|
||||
"Domain: Kubernetes",
|
||||
"Domain: Cloud",
|
||||
"Use Case: Threat Detection",
|
||||
"Tactic: Credential Access",
|
||||
"Tactic: Collection",
|
||||
"Resources: Investigation Guide",
|
||||
]
|
||||
timestamp_override = "event.ingested"
|
||||
type = "esql"
|
||||
|
||||
query = '''
|
||||
FROM logs-kubernetes.audit_logs-* metadata _id, _version, _index
|
||||
| WHERE STARTS_WITH(kubernetes.audit.user.username, "system:serviceaccount:azure-arc:")
|
||||
AND kubernetes.audit.objectRef.resource IN ("secrets", "configmaps")
|
||||
AND kubernetes.audit.verb IN ("get", "list", "create", "update", "patch", "delete")
|
||||
AND kubernetes.audit.objectRef.namespace NOT IN ("azure-arc", "azure-arc-release", "kube-system")
|
||||
AND NOT STARTS_WITH(kubernetes.audit.objectRef.name, "sh.helm.release.v1")
|
||||
|
||||
| STATS
|
||||
Esql.verb_values = VALUES(kubernetes.audit.verb),
|
||||
Esql.resource_type_values = VALUES(kubernetes.audit.objectRef.resource),
|
||||
Esql.resource_name_values = VALUES(kubernetes.audit.objectRef.name),
|
||||
Esql.namespace_values = VALUES(kubernetes.audit.objectRef.namespace),
|
||||
Esql.acting_user_values = VALUES(kubernetes.audit.user.username),
|
||||
Esql.user_agent_values = VALUES(kubernetes.audit.userAgent),
|
||||
Esql.source_ips_values = VALUES(kubernetes.audit.sourceIPs),
|
||||
Esql.response_code_values = VALUES(kubernetes.audit.responseStatus.code),
|
||||
Esql.timestamp_first_seen = MIN(@timestamp),
|
||||
Esql.timestamp_last_seen = MAX(@timestamp),
|
||||
Esql.event_count = COUNT(*)
|
||||
BY kubernetes.audit.impersonatedUser.username
|
||||
|
||||
| WHERE Esql.timestamp_first_seen >= NOW() - 9 minutes
|
||||
| KEEP *
|
||||
'''
|
||||
|
||||
|
||||
[[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/"
|
||||
[[rule.threat]]
|
||||
framework = "MITRE ATT&CK"
|
||||
[[rule.threat.technique]]
|
||||
id = "T1530"
|
||||
name = "Data from Cloud Storage"
|
||||
reference = "https://attack.mitre.org/techniques/T1530/"
|
||||
|
||||
|
||||
[rule.threat.tactic]
|
||||
id = "TA0009"
|
||||
name = "Collection"
|
||||
reference = "https://attack.mitre.org/tactics/TA0009/"
|
||||
|
||||
Reference in New Issue
Block a user