Unusual Azure VM Extension Detected
Identifies the first time a given VM extension name is created or updated on an Azure virtual machine or VM scale set within the
rule's lookback window. VM extensions run with high privilege on the guest (SYSTEM on Windows, root on Linux) and are a
common code-execution and persistence primitive. The extension instance name is attacker-controlled and the Azure
activity log records only that name, not the publisher or type, so the control plane cannot reliably identify the
extension family (for example CustomScript). This rule therefore takes a type-agnostic ES|QL new-terms approach: it
derives the host and the extension instance name from azure.resource.name and alerts the first time a given
(host, extension name) pair is observed in the window, surfacing novel extension deployments while suppressing names a
host routinely uses.
Elastic rule (View on GitHub)
1[metadata]
2creation_date = "2026/06/15"
3integration = ["azure"]
4maturity = "production"
5updated_date = "2026/09/28"
6
7[rule]
8author = ["Elastic"]
9description = """
10Identifies the first time a given VM extension name is created or updated on an Azure virtual machine or VM scale set within the
11rule's lookback window. VM extensions run with high privilege on the guest (SYSTEM on Windows, root on Linux) and are a
12common code-execution and persistence primitive. The extension instance name is attacker-controlled and the Azure
13activity log records only that name, not the publisher or type, so the control plane cannot reliably identify the
14extension family (for example CustomScript). This rule therefore takes a type-agnostic ES|QL new-terms approach: it
15derives the host and the extension instance name from `azure.resource.name` and alerts the first time a given
16(host, extension name) pair is observed in the window, surfacing novel extension deployments while suppressing names a
17host routinely uses.
18"""
19false_positives = [
20 """
21 Legitimate provisioning, patching, and configuration-management automation may deploy an extension to a
22 host for the first time. The first occurrence per host will alert. Baseline expected automation principals and hosts
23 and exclude verified-benign ones.
24 """,
25]
26from = "now-7d"
27interval = "5m"
28language = "esql"
29license = "Elastic License v2"
30name = "Unusual Azure VM Extension Detected"
31note = """## Triage and analysis
32
33### Investigating Unusual Azure VM Extension Detected
34
35Identifies the first time a given VM extension name is created or updated on an Azure virtual machine or VM scale set within the
36rule's lookback window. VM extensions run with high privilege on the guest (SYSTEM on Windows, root on Linux) and are a
37common code-execution and persistence primitive. The extension instance name is attacker-controlled and the Azure
38activity log records only that name, not the publisher or type, so the control plane cannot reliably identify the
39extension family (for example CustomScript). This rule therefore takes a type-agnostic ES|QL new-terms approach: it
40derives the host and the extension instance name from `azure.resource.name` and alerts the first time a given
41(host, extension name) pair is observed in the window, surfacing novel extension deployments while suppressing names a
42host routinely uses.
43
44### Possible investigation steps
45
46- Identify the host (`Esql.vm_name`) and the full extension resource (`azure.resource.name` / `azure.resource.id`).
47- Identify the acting principal: `Esql.principal_id_values`, `Esql.principal_type_values` (User vs ServicePrincipal),
48 `Esql.appid_values`. Service principal or managed identity deployment is more suspicious than a known admin user.
49- Review the source: `Esql.source_ip_values`, `Esql.source_as_number_values`, `Esql.source_country_values`. Cloud
50 hosting, VPS, or anonymizing networks are more suspicious than known corporate egress.
51- Was this preceded by a Run Command invocation, role assignment, or other VM operations by the same principal?
52- Correlate with endpoint telemetry on the host: process activity parented by the Azure guest agent
53 (`WaAppAgent.exe` / `walinuxagent`) within ~120 seconds of the deployment.
54- Review the principal's Entra ID sign-in logs and RBAC role assignments on the subscription, resource group, and VM.
55- Retrieve the extension settings/protected settings from the VM (the activity log does not contain the script/settings
56 body) to assess intent.
57- Pivot on the VM for credential access, new local accounts, or outbound C2 connections following the deployment.
58
59### False positive analysis
60
61- This is a broad first-seen net: the first deployment of any extension name to a host alerts, so benign monitoring,
62 antimalware (Defender/MDE), AKS, DSC, or configuration-management extensions deployed by routine automation will
63 trigger. Baseline expected automation principals (`Esql.appid_values`) and extension names, and exclude verified ones.
64- Automation that generates a unique extension instance name per deployment produces a new (host, name) pair every time
65 and will recur; if benign, exclude by the deploying principal/appid or the known naming pattern rather than per host.
66- Newly provisioned VMs receiving their initial extension set are expected. Corroborate the deploying principal and
67 source before escalating, and treat deployments from known corporate egress by approved automation as lower confidence.
68
69### Response and remediation
70
71- If unauthorized, remove the extension, isolate the VM, rotate credentials reachable from it, and review RBAC on the affected scope.
72- Collect endpoint and activity log artifacts per incident procedures.
73"""
74references = [
75 "https://www.netspi.com/blog/technical-blog/adversary-simulation/7-ways-to-execute-command-on-azure-virtual-machine-virtual-machine-scale-sets/",
76 "https://learn.microsoft.com/en-us/azure/virtual-machines/extensions/custom-script-windows",
77 "https://learn.microsoft.com/en-us/azure/virtual-machines/extensions/overview",
78 "https://blog.pwnedlabs.io/diving-deep-into-azure-vm-attack-vectors",
79 "https://www.sysdig.com/blog/the-expendable-extension-name-azure-vmaccess-naming-chaos-password-resets-and-a-detection-gap",
80]
81risk_score = 47
82rule_id = "d29b0a67-178d-4381-92c5-02e9fd9a6ef6"
83severity = "medium"
84tags = [
85 "Domain: Cloud",
86 "Data Source: Azure",
87 "Data Source: Azure Activity Logs",
88 "Use Case: Threat Detection",
89 "Tactic: Execution",
90 "Tactic: Persistence",
91 "Resources: Investigation Guide",
92 "Noise: Medium",
93 "Performance: Fast",
94 "Profile: Recommended",
95 "Threat: Cloud VM Execution",
96 "Rule Type: ES|QL",
97 "Platform: Azure",
98]
99timestamp_override = "event.ingested"
100type = "esql"
101query = '''
102FROM logs-azure.activitylogs-*
103| WHERE event.dataset == "azure.activitylogs"
104 AND event.action IN (
105 "MICROSOFT.COMPUTE/VIRTUALMACHINES/EXTENSIONS/WRITE",
106 "MICROSOFT.COMPUTE/VIRTUALMACHINESCALESETS/EXTENSIONS/WRITE"
107 )
108 AND event.outcome IN ("success", "Success")
109// azure.resource.name is "<host>/EXTENSIONS/<instance-name>"; the instance name is attacker-controlled,
110// so key on the host (first path element) rather than the spoofable extension name
111| EVAL Esql.vm_name = MV_FIRST(SPLIT(azure.resource.name, "/"))
112| EVAL Esql.extension_name = MV_LAST(SPLIT(azure.resource.name, "/"))
113| STATS Esql.first_time_seen = MIN(@timestamp),
114 Esql.last_time_seen = MAX(@timestamp),
115 Esql.event_count = COUNT(*),
116 Esql.resource_name_values = VALUES(azure.resource.name),
117 Esql.resource_id_values = VALUES(azure.resource.id),
118 Esql.principal_id_values = VALUES(azure.activitylogs.identity.authorization.evidence.principal_id),
119 Esql.principal_type_values = VALUES(azure.activitylogs.identity.authorization.evidence.principal_type),
120 Esql.appid_values = VALUES(azure.activitylogs.identity.claims.appid),
121 Esql.source_ip_values = VALUES(source.ip),
122 Esql.source_as_number_values = VALUES(source.`as`.number),
123 Esql.source_country_values = VALUES(source.geo.country_name),
124 Esql.subscription_id_values = VALUES(azure.subscription_id)
125 BY Esql.vm_name, Esql.extension_name
126// new terms emulation: fire when the (host, extension name) pair's first_seen falls in the
127// current interval (schedule + ingest-lag buffer), i.e. it was absent from the rest of the 7-day window
128| EVAL Esql.recent_minutes = DATE_DIFF("minute", Esql.first_time_seen, NOW())
129| WHERE Esql.recent_minutes <= 10
130// surface real fields for the analyst and rule exceptions
131| EVAL azure.resource.name = MV_FIRST(Esql.resource_name_values),
132 azure.resource.id = MV_FIRST(Esql.resource_id_values),
133 source.ip = MV_FIRST(Esql.source_ip_values)
134| KEEP azure.resource.name, azure.resource.id, source.ip, Esql.*
135'''
136
137[rule.alert_suppression]
138group_by = ["azure.resource.name"]
139missing_fields_strategy = "suppress"
140
141[rule.alert_suppression.duration]
142unit = "m"
143value = 60
144
145[[rule.threat]]
146framework = "MITRE ATT&CK"
147
148[[rule.threat.technique]]
149id = "T1651"
150name = "Cloud Administration Command"
151reference = "https://attack.mitre.org/techniques/T1651/"
152
153[rule.threat.tactic]
154id = "TA0002"
155name = "Execution"
156reference = "https://attack.mitre.org/tactics/TA0002/"
157
158[[rule.threat]]
159framework = "MITRE ATT&CK"
160
161[[rule.threat.technique]]
162id = "T1037"
163name = "Boot or Logon Initialization Scripts"
164reference = "https://attack.mitre.org/techniques/T1037/"
165
166[rule.threat.tactic]
167id = "TA0003"
168name = "Persistence"
169reference = "https://attack.mitre.org/tactics/TA0003/"
Triage and analysis
Investigating Unusual Azure VM Extension Detected
Identifies the first time a given VM extension name is created or updated on an Azure virtual machine or VM scale set within the
rule's lookback window. VM extensions run with high privilege on the guest (SYSTEM on Windows, root on Linux) and are a
common code-execution and persistence primitive. The extension instance name is attacker-controlled and the Azure
activity log records only that name, not the publisher or type, so the control plane cannot reliably identify the
extension family (for example CustomScript). This rule therefore takes a type-agnostic ES|QL new-terms approach: it
derives the host and the extension instance name from azure.resource.name and alerts the first time a given
(host, extension name) pair is observed in the window, surfacing novel extension deployments while suppressing names a
host routinely uses.
Possible investigation steps
- Identify the host (
Esql.vm_name) and the full extension resource (azure.resource.name/azure.resource.id). - Identify the acting principal:
Esql.principal_id_values,Esql.principal_type_values(User vs ServicePrincipal),Esql.appid_values. Service principal or managed identity deployment is more suspicious than a known admin user. - Review the source:
Esql.source_ip_values,Esql.source_as_number_values,Esql.source_country_values. Cloud hosting, VPS, or anonymizing networks are more suspicious than known corporate egress. - Was this preceded by a Run Command invocation, role assignment, or other VM operations by the same principal?
- Correlate with endpoint telemetry on the host: process activity parented by the Azure guest agent
(
WaAppAgent.exe/walinuxagent) within ~120 seconds of the deployment. - Review the principal's Entra ID sign-in logs and RBAC role assignments on the subscription, resource group, and VM.
- Retrieve the extension settings/protected settings from the VM (the activity log does not contain the script/settings body) to assess intent.
- Pivot on the VM for credential access, new local accounts, or outbound C2 connections following the deployment.
False positive analysis
- This is a broad first-seen net: the first deployment of any extension name to a host alerts, so benign monitoring,
antimalware (Defender/MDE), AKS, DSC, or configuration-management extensions deployed by routine automation will
trigger. Baseline expected automation principals (
Esql.appid_values) and extension names, and exclude verified ones. - Automation that generates a unique extension instance name per deployment produces a new (host, name) pair every time and will recur; if benign, exclude by the deploying principal/appid or the known naming pattern rather than per host.
- Newly provisioned VMs receiving their initial extension set are expected. Corroborate the deploying principal and source before escalating, and treat deployments from known corporate egress by approved automation as lower confidence.
Response and remediation
- If unauthorized, remove the extension, isolate the VM, rotate credentials reachable from it, and review RBAC on the affected scope.
- Collect endpoint and activity log artifacts per incident procedures.
References
Related rules
- Azure VM Extension CRUD Operation with Unusual Source ASN
- Azure VM Extension Deployment by User
- Azure VM Managed Run Command Created or Updated with Unusual Principal
- Azure Run Command Correlated with Process Execution
- Azure Compute Snapshot Deletions by User