diff --git a/rules/integrations/azure/execution_azure_vm_extension_unusual_for_host.toml b/rules/integrations/azure/execution_azure_vm_extension_unusual_for_host.toml new file mode 100644 index 00000000000..c4a364bd423 --- /dev/null +++ b/rules/integrations/azure/execution_azure_vm_extension_unusual_for_host.toml @@ -0,0 +1,164 @@ +[metadata] +creation_date = "2026/06/15" +integration = ["azure"] +maturity = "production" +updated_date = "2026/06/15" + +[rule] +author = ["Elastic"] +description = """ +Identifies the first time a given VM extension name is created or updated on an Azure virtual machine or VM scale set +historically. 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. +""" +false_positives = [ + """ + Legitimate provisioning, patching, and configuration-management automation may deploy a CustomScript extension to a + host for the first time. The first occurrence per host will alert. Baseline expected automation principals and hosts + and exclude verified-benign ones. + """, +] +from = "now-7d" +interval = "5m" +language = "esql" +license = "Elastic License v2" +name = "Unusual Azure VM Extension Detected" +note = """## 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 +historically. 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 = [ + "https://www.netspi.com/blog/technical-blog/adversary-simulation/7-ways-to-execute-command-on-azure-virtual-machine-virtual-machine-scale-sets/", + "https://learn.microsoft.com/en-us/azure/virtual-machines/extensions/custom-script-windows", + "https://learn.microsoft.com/en-us/azure/virtual-machines/extensions/overview", + "https://blog.pwnedlabs.io/diving-deep-into-azure-vm-attack-vectors", + "https://www.sysdig.com/blog/the-expendable-extension-name-azure-vmaccess-naming-chaos-password-resets-and-a-detection-gap", +] +risk_score = 47 +rule_id = "d29b0a67-178d-4381-92c5-02e9fd9a6ef6" +severity = "medium" +tags = [ + "Domain: Cloud", + "Data Source: Azure", + "Data Source: Azure Activity Logs", + "Use Case: Threat Detection", + "Tactic: Execution", + "Tactic: Persistence", + "Resources: Investigation Guide", +] +timestamp_override = "event.ingested" +type = "esql" +query = ''' +FROM logs-azure.activitylogs-* +| WHERE event.dataset == "azure.activitylogs" + AND event.action IN ( + "MICROSOFT.COMPUTE/VIRTUALMACHINES/EXTENSIONS/WRITE", + "MICROSOFT.COMPUTE/VIRTUALMACHINESCALESETS/EXTENSIONS/WRITE" + ) + AND event.outcome IN ("success", "Success") +// azure.resource.name is "/EXTENSIONS/"; the instance name is attacker-controlled, +// so key on the host (first path element) rather than the spoofable extension name +| EVAL Esql.vm_name = MV_FIRST(SPLIT(azure.resource.name, "/")) +| EVAL Esql.extension_name = MV_LAST(SPLIT(azure.resource.name, "/")) +| STATS Esql.first_time_seen = MIN(@timestamp), + Esql.last_time_seen = MAX(@timestamp), + Esql.event_count = COUNT(*), + Esql.resource_name_values = VALUES(azure.resource.name), + Esql.resource_id_values = VALUES(azure.resource.id), + Esql.principal_id_values = VALUES(azure.activitylogs.identity.authorization.evidence.principal_id), + Esql.principal_type_values = VALUES(azure.activitylogs.identity.authorization.evidence.principal_type), + Esql.appid_values = VALUES(azure.activitylogs.identity.claims.appid), + Esql.source_ip_values = VALUES(source.ip), + Esql.source_as_number_values = VALUES(source.`as`.number), + Esql.source_country_values = VALUES(source.geo.country_name), + Esql.subscription_id_values = VALUES(azure.subscription_id) + BY Esql.vm_name, Esql.extension_name +// new terms emulation: fire only when the (host, extension name) pair is the single occurrence in the +// 7-day window (event_count == 1) and it is recent (within the schedule interval + ingest-lag buffer) +| EVAL Esql.recent_minutes = DATE_DIFF("minute", Esql.first_time_seen, NOW()) +| WHERE Esql.recent_minutes <= 10 AND Esql.event_count == 1 +// surface real fields for the analyst and rule exceptions +| EVAL azure.resource.name = MV_FIRST(Esql.resource_name_values), + azure.resource.id = MV_FIRST(Esql.resource_id_values), + source.ip = MV_FIRST(Esql.source_ip_values) +| KEEP azure.resource.name, azure.resource.id, source.ip, Esql.* +''' + +[rule.alert_suppression] +group_by = ["azure.resource.name"] +missing_fields_strategy = "suppress" + +[rule.alert_suppression.duration] +unit = "m" +value = 60 + +[[rule.threat]] +framework = "MITRE ATT&CK" + +[[rule.threat.technique]] +id = "T1651" +name = "Cloud Administration Command" +reference = "https://attack.mitre.org/techniques/T1651/" + +[rule.threat.tactic] +id = "TA0002" +name = "Execution" +reference = "https://attack.mitre.org/tactics/TA0002/" + +[[rule.threat]] +framework = "MITRE ATT&CK" + +[[rule.threat.technique]] +id = "T1037" +name = "Boot or Logon Initialization Scripts" +reference = "https://attack.mitre.org/techniques/T1037/" + +[rule.threat.tactic] +id = "TA0003" +name = "Persistence" +reference = "https://attack.mitre.org/tactics/TA0003/" diff --git a/rules/windows/execution_azure_customscript_extension_suspicious_descendant.toml b/rules/windows/execution_azure_customscript_extension_suspicious_descendant.toml new file mode 100644 index 00000000000..73706ae1987 --- /dev/null +++ b/rules/windows/execution_azure_customscript_extension_suspicious_descendant.toml @@ -0,0 +1,146 @@ +[metadata] +creation_date = "2026/06/17" +integration = ["endpoint"] +maturity = "production" +updated_date = "2026/06/17" + +[rule] +author = ["Elastic"] +description = """ +Identifies a suspicious process executing as a descendant of the Azure VM CustomScript extension handler +(CustomScriptHandler.exe) on a Windows host. The Azure CustomScript extension runs an attacker-supplied script with high +privilege (SYSTEM) via the guest agent, and is a common cloud-to-host code-execution and persistence primitive. Because +the extension's resource name is attacker-controlled and absent from on-host telemetry, this rule anchors on the +type-bearing handler binary ('Microsoft.Compute.CustomScriptExtension\\...\\CustomScriptHandler.exe') rather than the +spoofable extension name, making it resistant to renaming. CustomScript legitimately launches PowerShell and cmd, so the +rule fires only when the descendant is an execution-proxy, download, or discovery LOLBin, or PowerShell exhibiting +suspicious tradecraft. +""" +from = "now-9m" +index = ["logs-endpoint.events.process-*"] +language = "eql" +license = "Elastic License v2" +name = "Suspicious Child Process via Azure VM CustomScript Extension" +note = """## Triage and analysis + +### Investigating Suspicious Child Process via Azure VM CustomScript Extension + +The Azure CustomScript extension executes a script as SYSTEM via the guest agent. The extension's resource name is +attacker-controlled and not present on the host, so this rule anchors on the handler binary path +(`Microsoft.Compute.CustomScriptExtension\\...\\CustomScriptHandler.exe`), which is rename-proof, and alerts when a +LOLBin or suspicious PowerShell runs anywhere in its process tree. + +### Possible investigation steps + +- Review the full process tree from `CustomScriptHandler.exe` to the alerting process, including `process.command_line` + and `process.args`. +- Identify the descendant: execution proxies (`mshta`, `regsvr32`, `rundll32`, `installutil`, `msbuild`), download tools + (`certutil`, `bitsadmin`), script hosts (`wscript`, `cscript`), or discovery utilities (`whoami`, `net`, `nltest`, + `wmic`) are not expected children of a benign CustomScript payload. +- Correlate with the control-plane event: a `MICROSOFT.COMPUTE/VIRTUALMACHINES/EXTENSIONS/WRITE` in + `logs-azure.activitylogs-*` for this host around the same time, and the acting principal/source behind it. +- Retrieve the extension's settings/protectedSettings from the VM (the activity log does not contain the script body) to + assess intent. +- Pivot on the host for credential access, new local accounts, persistence, or outbound C2 following the execution. +- Review who deployed the extension (Entra sign-in logs and RBAC for the principal in the correlated activity log event). + +### False positive analysis + +- Infrastructure-as-code and configuration-management scripts deployed via CustomScript may legitimately run discovery + utilities (`whoami`, `net`, `nltest`, `systeminfo`, `wmic`, `tasklist`, `arp`) for bootstrap or inventory. If the + activity recurs from known automation, baseline it and exclude by `process.command_line`/`process.args`. +- Software installation and bootstrapping via CustomScript can invoke `msbuild`, `installutil`, `regsvr32`, `regasm`, + `regsvcs`, `certutil`, or `bitsadmin` to build, register, or download legitimate components. Verify the target + file/URL and, if benign, scope the exclusion to the specific command or signed binary rather than the whole LOLBin. +- Legitimate setup scripts (DSC bootstrap, agent installers) may use PowerShell download cradles + (`Invoke-WebRequest`, `DownloadString`, `-EncodedCommand`) against trusted internal or Microsoft endpoints. Confirm + the destination host and content before excluding, and exclude by the specific command line, not by host. +- A known automation principal deploying the extension from expected corporate egress (corroborated by the correlated + `MICROSOFT.COMPUTE/VIRTUALMACHINES/EXTENSIONS/WRITE` and an approved change) lowers confidence, but still review the + executed content. Prefer narrow, command- or argument-scoped exclusions over broad host or LOLBin exclusions, since + the same execution chain is exactly what an attacker abuses. + +### Response and remediation + +- If unauthorized, remove the extension, isolate the host, rotate credentials reachable from it, and review RBAC on the + affected subscription/resource group. +""" +references = [ + "https://blog.pwnedlabs.io/diving-deep-into-azure-vm-attack-vectors", + "https://www.sysdig.com/blog/the-expendable-extension-name-azure-vmaccess-naming-chaos-password-resets-and-a-detection-gap", + "https://learn.microsoft.com/en-us/azure/virtual-machines/extensions/custom-script-windows", +] +risk_score = 47 +rule_id = "b4d4f0fb-908e-4cd1-ac8f-795c0433db0a" +severity = "medium" +tags = [ + "Domain: Endpoint", + "OS: Windows", + "Use Case: Threat Detection", + "Tactic: Execution", + "Tactic: Defense Evasion", + "Data Source: Elastic Defend", + "Resources: Investigation Guide", +] +timestamp_override = "event.ingested" +type = "eql" +query = ''' +sequence by host.id with maxspan=1m + /* Azure CustomScript extension handler */ + [process where host.os.type == "windows" and event.type == "start" and + (process.name : "CustomScriptHandler.exe" or + process.executable : "?:\\Packages\\Plugins\\*CustomScript*\\*\\CustomScriptHandler.exe")] by process.entity_id + /* Abused LOLBin / suspicious PowerShell anywhere in its tree */ + [process where host.os.type == "windows" and event.type == "start" and + ( + process.name : ("mshta.exe", "regsvr32.exe", "rundll32.exe", "installutil.exe", "msbuild.exe", "regasm.exe", + "regsvcs.exe", "wscript.exe", "cscript.exe", "bitsadmin.exe", "nltest.exe", "whoami.exe", + "net.exe", "net1.exe", "wmic.exe", "systeminfo.exe", "quser.exe", "arp.exe", "tasklist.exe") or + (process.name : "certutil.exe" and process.args : ("*urlcache*", "*-decode*", "*-encode*")) or + (process.name : ("powershell.exe", "pwsh.exe") and + process.command_line : ("*-enc*", "*EncodedCommand*", "*FromBase64String*", "*DownloadString*", "*DownloadFile*", + "*Invoke-Expression*", "*IEX *", "* -w hidden*", "*WindowStyle Hidden*", "*Net.WebClient*", + "*Invoke-WebRequest*", "*Start-BitsTransfer*")) + )] by process.Ext.ancestry +''' + +[[rule.threat]] +framework = "MITRE ATT&CK" + +[[rule.threat.technique]] +id = "T1651" +name = "Cloud Administration Command" +reference = "https://attack.mitre.org/techniques/T1651/" + +[[rule.threat.technique]] +id = "T1059" +name = "Command and Scripting Interpreter" +reference = "https://attack.mitre.org/techniques/T1059/" + +[[rule.threat.technique.subtechnique]] +id = "T1059.001" +name = "PowerShell" +reference = "https://attack.mitre.org/techniques/T1059/001/" + +[[rule.threat.technique.subtechnique]] +id = "T1059.003" +name = "Windows Command Shell" +reference = "https://attack.mitre.org/techniques/T1059/003/" + +[rule.threat.tactic] +id = "TA0002" +name = "Execution" +reference = "https://attack.mitre.org/tactics/TA0002/" + +[[rule.threat]] +framework = "MITRE ATT&CK" + +[[rule.threat.technique]] +id = "T1218" +name = "System Binary Proxy Execution" +reference = "https://attack.mitre.org/techniques/T1218/" + +[rule.threat.tactic] +id = "TA0005" +name = "Defense Evasion" +reference = "https://attack.mitre.org/tactics/TA0005/"