Azure Excessive IP and VM Discovery


Description

Detects excessive read operations on Azure public IP addresses and virtual machines. Adversaries may enumerate public IPs and virtual machines to identify external attack surfaces, map network topology, and identify potential targets for exploitation. This reconnaissance pattern often precedes lateral movement attempts, privilege escalation, or targeted attacks. The threshold-based detection triggers when the same resource type is read excessively within a time window.

Query · python

from panther_azureactivity_helpers import azure_activity_alert_context, azure_activity_success

PUBLIC_IP_READ = "MICROSOFT.NETWORK/PUBLICIPADDRESSES/READ"
VM_READ = "MICROSOFT.COMPUTE/VIRTUALMACHINES/READ"


def rule(event):
    operation = event.get("operationName", "").upper()
    return all(
        [
            operation in [PUBLIC_IP_READ, VM_READ],
            azure_activity_success(event),
        ]
    )


def title(event):
    resource_id = event.get("resourceId", "<UNKNOWN_RESOURCE>")
    operation = event.get("operationName", "").upper()

    if operation == PUBLIC_IP_READ:
        resource_type = "Public IP Address"
    elif operation == VM_READ:
        resource_type = "Virtual Machine"
    else:
        resource_type = "Resource"

    return f"Azure Excessive {resource_type} Read on [{resource_id}]"


def alert_context(event):
    return azure_activity_alert_context(event)

Analyst notes

  1. Query Azure Monitor Activity logs for all public IP address and virtual machine read operations by the callerIpAddress in the 24 hours before and after the alert to identify the scope of reconnaissance activity
  2. Find all resource read operations (network security groups, route tables, virtual networks, storage accounts) by the same callerIpAddress in the 6 hours before and after the alert to determine if this is part of broader reconnaissance
  3. Check if the callerIpAddress has performed similar high-volume read operations in the past 90 days to establish if this is normal administrative activity or anomalous behavior
Raw source Azure Excessive IP and VM Discovery · Panther Python
Esc
Published by panther-labs/panther-analysis ↗, licensed under Apache 2.0 ↗. Reproduced here unmodified.
AnalysisType: rule
Filename: azure_network_ip_discovery.py
RuleID: "Azure.MonitorActivity.Network.IPDiscovery"
DisplayName: "Azure Excessive IP and VM Discovery"
Enabled: true
LogTypes:
  - Azure.MonitorActivity
Severity: Medium
Threshold: 50
Description: >
  Detects excessive read operations on Azure public IP addresses and virtual machines. Adversaries
  may enumerate public IPs and virtual machines to identify external attack surfaces, map network
  topology, and identify potential targets for exploitation. This reconnaissance pattern often
  precedes lateral movement attempts, privilege escalation, or targeted attacks. The threshold-based
  detection triggers when the same resource type is read excessively within a time window.
Reports:
  MITRE ATT&CK:
    - TA0007:T1046 # Discovery: Network Service Discovery
    - TA0007:T1018 # Discovery: Remote System Discovery
    - TA0043:T1595.002 # Reconnaissance: Active Scanning - Vulnerability Scanning
Tags:
  - AZT102
  - Discovery
  - Reconnaissance
  - Network Service Discovery
  - Remote System Discovery
  - Active Scanning
  - Vulnerability Scanning
Runbook: |
  1. Query Azure Monitor Activity logs for all public IP address and virtual machine read operations by the callerIpAddress in the 24 hours before and after the alert to identify the scope of reconnaissance activity
  2. Find all resource read operations (network security groups, route tables, virtual networks, storage accounts) by the same callerIpAddress in the 6 hours before and after the alert to determine if this is part of broader reconnaissance
  3. Check if the callerIpAddress has performed similar high-volume read operations in the past 90 days to establish if this is normal administrative activity or anomalous behavior
Reference: https://microsoft.github.io/Azure-Threat-Research-Matrix/Reconnaissance/AZT101/AZT102/
SummaryAttributes:
  - resourceId
  - location
  - callerIpAddress
  - correlationId
Tests:
  - Name: Public IP Address Read Success
    ExpectedResult: true
    Log:
      {
        "time": "2025-12-22T10:30:00.0000000Z",
        "resourceId": "/subscriptions/12345678-1234-1234-1234-123456789abc/resourceGroups/network-rg/providers/Microsoft.Network/publicIPAddresses/myPublicIP",
        "operationName": "MICROSOFT.NETWORK/PUBLICIPADDRESSES/READ",
        "operationVersion": "2021-05-01",
        "category": "Administrative",
        "resultType": "Success",
        "resultSignature": "200",
        "callerIpAddress": "1.1.1.1",
        "correlationId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
        "level": "Informational",
        "location": "eastus",
        "tenantId": "87654321-4321-4321-4321-111111111111"
      }
  - Name: Virtual Machine Read Success
    ExpectedResult: true
    Log:
      {
        "time": "2025-12-22T11:00:00.0000000Z",
        "resourceId": "/subscriptions/11111111-1111-1111-1111-111111111111/resourceGroups/compute-rg/providers/Microsoft.Compute/virtualMachines/myVM",
        "operationName": "MICROSOFT.COMPUTE/VIRTUALMACHINES/READ",
        "operationVersion": "2021-07-01",
        "category": "Administrative",
        "resultType": "Success",
        "resultSignature": "200",
        "callerIpAddress": "2.2.2.2",
        "correlationId": "b2c3d4e5-f6a7-8901-bcde-f23456789012",
        "level": "Informational",
        "location": "westeurope",
        "tenantId": "22222222-2222-2222-2222-222222222222"
      }
  - Name: Public IP Address Read Case Insensitive
    ExpectedResult: true
    Log:
      {
        "time": "2025-12-22T11:15:00.0000000Z",
        "resourceId": "/subscriptions/33333333-3333-3333-3333-333333333333/resourceGroups/network-rg/providers/Microsoft.Network/publicIPAddresses/pip-prod",
        "operationName": "microsoft.network/publicipaddresses/read",
        "category": "Administrative",
        "resultType": "Succeeded",
        "callerIpAddress": "3.3.3.3",
        "correlationId": "c3d4e5f6-a7b8-9012-cdef-333333333333",
        "level": "Information",
        "location": "centralus",
        "tenantId": "33333333-3333-3333-3333-333333333333"
      }
  - Name: Virtual Machine Read Case Insensitive
    ExpectedResult: true
    Log:
      {
        "time": "2025-12-22T11:30:00.0000000Z",
        "resourceId": "/subscriptions/44444444-4444-4444-4444-444444444444/resourceGroups/compute-rg/providers/Microsoft.Compute/virtualMachines/vm-prod-001",
        "operationName": "microsoft.compute/virtualmachines/read",
        "category": "Administrative",
        "resultType": "Succeeded",
        "callerIpAddress": "4.4.4.4",
        "correlationId": "d4e5f6a7-b8c9-0123-def0-444444444444",
        "level": "Information",
        "location": "westus",
        "tenantId": "44444444-4444-4444-4444-444444444444"
      }
  - Name: Different Operation
    ExpectedResult: false
    Log:
      {
        "time": "2025-12-22T14:00:00.0000000Z",
        "resourceId": "/subscriptions/88888888-8888-8888-8888-888888888888/resourceGroups/storage-rg/providers/Microsoft.Storage/storageAccounts/mystorageaccount",
        "operationName": "MICROSOFT.STORAGE/STORAGEACCOUNTS/READ",
        "category": "Administrative",
        "resultType": "Success",
        "callerIpAddress": "8.8.8.8",
        "correlationId": "b8c9d0e1-f2a3-4567-1234-888888888888",
        "tenantId": "88888888-8888-8888-8888-888888888888"
      }

# ------ paired body: azure_network_ip_discovery.py ------

from panther_azureactivity_helpers import azure_activity_alert_context, azure_activity_success

PUBLIC_IP_READ = "MICROSOFT.NETWORK/PUBLICIPADDRESSES/READ"
VM_READ = "MICROSOFT.COMPUTE/VIRTUALMACHINES/READ"


def rule(event):
    operation = event.get("operationName", "").upper()
    return all(
        [
            operation in [PUBLIC_IP_READ, VM_READ],
            azure_activity_success(event),
        ]
    )


def title(event):
    resource_id = event.get("resourceId", "<UNKNOWN_RESOURCE>")
    operation = event.get("operationName", "").upper()

    if operation == PUBLIC_IP_READ:
        resource_type = "Public IP Address"
    elif operation == VM_READ:
        resource_type = "Virtual Machine"
    else:
        resource_type = "Resource"

    return f"Azure Excessive {resource_type} Read on [{resource_id}]"


def alert_context(event):
    return azure_activity_alert_context(event)

Detection rules belong to the projects that publish them and remain under their own licenses. This site indexes and links to them; it claims no rights in them.