Skip to content
Open
1 change: 1 addition & 0 deletions detection_rules/etc/non-ecs-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,7 @@
"azure.platformlogs.identity.type": "keyword",
"azure.platformlogs.properties.userAgentHeader": "keyword",
"azure.platformlogs.statusCode": "keyword",
"azure.resource.id": "keyword",
"azure.platformlogs.properties.accountName": "keyword",
"azure.platformlogs.properties.log.stage": "keyword",
"azure.platformlogs.properties.log.verb": "keyword",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
[metadata]
creation_date = "2026/08/17"
integration = ["endpoint"]
maturity = "production"
updated_date = "2026/08/17"

[rule]
author = ["Elastic"]
description = """
Identifies shells, LOLBins, GTFOBins, and scripting runtimes connecting to the Azure WireServer / HostGAPlugin address
168.63.129.16 on ports 80 or 32526. The guest agent uses this fabric endpoint for GoalState, certificates, and
vmSettings. Adversaries with code execution on an Azure VM (including via Run Command) use curl, PowerShell, openssl,
bun, or similar tools to enumerate versions, pull transport certificates, and read HostGAPlugin /vmSettings. Azure
guest-agent binaries and system python used by waagent are excluded. Descendants of the guest agent are not excluded:
Run Command payloads execute in that tree.
"""
false_positives = [
"""
Custom inventory or health scripts that call WireServer with curl or PowerShell. Validate the command line and
exclude the specific signed binary or scheduled task after review.
""",
"""
Break-glass troubleshooting by administrators using curl against 168.63.129.16. Confirm the change window and
exclude the admin host or user if the activity is authorized.
""",
]
from = "now-9m"
index = ["logs-endpoint.events.network-*"]
language = "kuery"
license = "Elastic License v2"
name = "Azure WireServer Unusual Process Connection"
note = """## Triage and analysis

### Investigating Azure WireServer Unusual Process Connection

`168.63.129.16` is the Azure host-only WireServer (TCP 80) and HostGAPlugin (TCP 32526) address. Elastic Defend
network events record the destination IP, port, and initiating process. They do not include the HTTP path; pair this
alert with Network Packet Capture HTTP events when available (`comp=certificates`, `/vmSettings`, `/versions`).

Do not treat "child of waagent / WindowsAzureGuestAgent" as benign. Azure Run Command and Custom Script Extension
launch attacker scripts as descendants of those agents. Exclude only the agent binaries themselves, which this query
already omits by matching curl, PowerShell, and similar tools.

`process.Ext.ancestry` is often empty on these network events, so EQL `descendant of` is not reliable here.

### Possible investigation steps

- Review `process.name`, `process.executable`, and `process.command_line` on nearby process start events. Look for
`comp=certificates`, `32526`, `vmSettings`, `LinuxTransport`, or `openssl cms -decrypt`.
- Note `destination.port`: 32526 from curl or PowerShell is uncommon for legitimate guest-agent traffic (agents use
`WaAppAgent.exe`, `WindowsAzureGuestAgent.exe`, `CollectGuestLogs.exe`, or `/usr/bin/python3.10` / waagent).
- Correlate with `169.254.169.254` IMDS access from the same process, especially `/metadata/v1/instanceinfo` (no
Metadata header) or `/metadata/identity/oauth2/token`.
- Check Azure Activity Logs for `runCommand/action` or extensions/write against this VM.
- Search StorageRead platform logs for subsequent SAS GetBlob of vmsettings or cse objects.

### False positive analysis

- In-house monitoring that wraps curl to WireServer. Exclude by `process.executable` or a signed parent after
validating the script contents.
- Do not exclude all children of the guest agent; that hides Run Command abuse.

### Response and remediation

- Isolate the VM, rotate its managed identity and any SAS recovered from vmSettings, and review extension
protectedSettings for injected configuration.
- Remove unauthorized Run Command resources and Custom Script extensions.
- Consider Azure Metadata Security Protocol (audit/enforce) to restrict which processes may call WireServer.
"""
references = [
"https://www.netspi.com/blog/technical-blog/cloud-pentesting/decrypting-vm-extension-settings-with-azure-wireserver/",
"https://cybercx.com.au/blog/azure-ssrf-metadata/",
"https://cloud.google.com/blog/topics/threat-intelligence/escalating-privileges-azure-kubernetes-services",
"https://learn.microsoft.com/en-us/azure/virtual-network/what-is-ip-address-168-63-129-16",
]
risk_score = 47
rule_id = "2dba3edf-1e23-4ead-a76f-458ef2060d24"
severity = "medium"
tags = [
"Domain: Endpoint",
"Domain: Cloud",
"OS: Linux",
"OS: Windows",
"Platform: Azure",
"Platform: Windows",
"Use Case: Threat Detection",
"Tactic: Credential Access",
"Tactic: Discovery",
"Data Source: Elastic Defend",
"Resources: Investigation Guide",
]
timestamp_override = "event.ingested"
type = "new_terms"

query = '''
event.category: network and host.os.type: (linux or windows) and
destination.ip: "168.63.129.16" and destination.port: (80 or 32526) and
(
process.name: (
bash or dash or sh or tcsh or csh or zsh or ksh or fish or mksh or busybox or
bun or bun.exe or node or node.exe or nodejs or deno or deno.exe or
java or java.exe or javaw or javaw.exe or
curl or curl.exe or wget or wget.exe or
powershell.exe or pwsh.exe or pwsh or cmd.exe or
certutil.exe or bitsadmin.exe or mshta.exe or rundll32.exe or
wscript.exe or cscript.exe or regsvr32.exe or
openssl or openssl.exe or nc or ncat or netcat or socat or
python.exe or pythonw.exe or perl or perl.exe or ruby or ruby.exe or
php or php.exe or lua or lua.exe
) or
process.executable: (
./* or /tmp/* or /var/tmp/* or /dev/shm/* or /run/* or /var/run/* or
/home/*/* or /root/* or "C:\\Users\\*" or "?:\\ProgramData\\*"
)
) and
not process.name: (
WaAppAgent.exe or WindowsAzureGuestAgent.exe or WaSecAgentProv.exe or
CollectGuestLogs.exe or waagent or WALinuxAgent or systemd-resolved
) and
not process.executable: (
/usr/sbin/waagent or /usr/bin/waagent or /usr/bin/python3* or
"?:\\WindowsAzure\\Packages\\*" or "?:\\WindowsAzure\\SecAgent\\*"
)
'''


[[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.005"
name = "Cloud Instance Metadata API"
reference = "https://attack.mitre.org/techniques/T1552/005/"



[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 = "T1082"
name = "System Information Discovery"
reference = "https://attack.mitre.org/techniques/T1082/"

[[rule.threat.technique]]
id = "T1580"
name = "Cloud Infrastructure Discovery"
reference = "https://attack.mitre.org/techniques/T1580/"


[rule.threat.tactic]
id = "TA0007"
name = "Discovery"
reference = "https://attack.mitre.org/tactics/TA0007/"

[rule.new_terms]
field = "new_terms_fields"
value = ["host.id", "process.executable"]
[[rule.new_terms.history_window_start]]
field = "history_window_start"
value = "now-7d"


Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
[metadata]
creation_date = "2026/08/17"
integration = ["azure"]
maturity = "production"
updated_date = "2026/08/17"

[rule]
author = ["Elastic"]
description = """
Identifies the first time an Azure Storage resource receives an anonymous data-plane read (GetBlob and related Get or
List operations). Anonymous requests are used to probe public containers and to test stolen blob URLs before a SAS is
appended. First-seen resource ID keeps volume down while still covering WireServer-related probes of status or extension
blobs.
"""
false_positives = [
"""
Intentionally public containers (static websites, public datasets) generate anonymous GetBlob and ListBlobs.
Baseline those storage accounts and exclude `azure.resource.id` after review.
""",
"""
Internet-wide scanners will produce first-seen anonymous access on newly created or newly logged accounts. Confirm
whether the account is meant to be public.
""",
]
from = "now-9m"
index = ["logs-azure.platformlogs-*"]
language = "kuery"
license = "Elastic License v2"
name = "Azure Storage Anonymous Blob Access to Unusual Resource"
note = """## Triage and analysis

### Investigating Azure Storage Anonymous Blob Access to Unusual Resource

StorageRead platform logs record `AuthenticationType` as Anonymous when no SAS, OAuth, or account key is presented.
A first-seen `azure.resource.id` (typically the blob service
`/subscriptions/.../storageAccounts/<account>/blobServices/default`) means this resource has not had anonymous Get or
List traffic in the history window.

WireServer SAS-replay chains often start with an anonymous GetBlob (HTTP 409/403) against the same object, then a
SAS 200. This rule does not require guest-agent path strings; those lab container names are not production
observables.

`source.ip` is often empty. Use `source.address` (`ip:port`).

### Possible investigation steps

- Review `event.action`, `azure.platformlogs.statusCode`, and `azure.platformlogs.uri`.
- HTTP 200 with Anonymous means the container or blob is publicly readable. HTTP 409/403 is a probe.
- Identify the account from `azure.resource.id` / `azure.resource.name` and check whether public access is intended.
- Search for SAS-authenticated GetBlob to the same account from the same source shortly after.
- If the URI contains `/$system/` or `md-hdd-`, correlate with WireServer access on VMs in the subscription.

### False positive analysis

- Public blob websites and CDN origins. Exclude the `azure.resource.id` for approved public accounts.
- New accounts that enable StorageRead for the first time will alert on the first scanner hit.

### Response and remediation

- Disable anonymous public access on accounts that should be private.
- If a follow-on SAS read exists, revoke that SAS and review how the URL was obtained.
- Keep StorageRead diagnostic logs enabled on storage accounts of interest.
"""
references = [
"https://learn.microsoft.com/en-us/azure/azure-monitor/reference/tables/storagebloblogs",
"https://learn.microsoft.com/en-us/azure/storage/blobs/anonymous-read-access-prevent",
"https://cybercx.com.au/blog/azure-ssrf-metadata/",
"https://www.netspi.com/blog/technical-blog/cloud-pentesting/decrypting-vm-extension-settings-with-azure-wireserver/",
]
risk_score = 47
rule_id = "eabaf807-e710-4f0f-8943-8d1b436d834a"
setup = """#### Required Azure Storage Diagnostic Logs

Enable StorageRead diagnostic logs on Azure Storage Accounts and stream them to the Event Hub used by the Azure
integration. Anonymous vs SAS is `azure.platformlogs.identity.type`.
"""
severity = "medium"
tags = [
"Domain: Cloud",
"Data Source: Azure",
"Data Source: Azure Platform Logs",
"Platform: Azure",
"Service: Azure Storage",
"Use Case: Threat Detection",
"Tactic: Discovery",
"Tactic: Collection",
"Resources: Investigation Guide",
]
timestamp_override = "event.ingested"
type = "new_terms"

query = '''
data_stream.dataset: azure.platformlogs and
azure.platformlogs.identity.type: Anonymous and
event.action: (
GetBlob or GetBlobMetadata or GetBlobProperties or GetBlockList or
GetPageRanges or QueryBlobContents or ListBlobs or
GetContainerProperties or GetContainerMetadata or GetContainerAcl
)
'''


[[rule.threat]]
framework = "MITRE ATT&CK"
[[rule.threat.technique]]
id = "T1580"
name = "Cloud Infrastructure Discovery"
reference = "https://attack.mitre.org/techniques/T1580/"


[rule.threat.tactic]
id = "TA0007"
name = "Discovery"
reference = "https://attack.mitre.org/tactics/TA0007/"
[[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/"

[rule.new_terms]
field = "new_terms_fields"
value = ["azure.resource.id"]
[[rule.new_terms.history_window_start]]
field = "history_window_start"
value = "now-7d"


Loading
Loading