Query Details

HUNT 05 TI Management Plane Extended

Query

// Hunt     : Hunt - TI Indicator Match in Azure Management Plane - Extended 90 Days
// Tactics  : InitialAccess,Persistence,Impact
// MITRE    : T1078,T1098,T1496
// Purpose  : Extended 90-day TI correlation against ThreatIntelIndicators (STIX 2.1) with lower confidence threshold (>=50). No IsActive/Revoked filters -- intentionally covers expired and revoked indicators to surface historical management plane access from adversary-controlled IPs before indicators were activated or feeds were ingested.
//==========================================================================================

let PrivateRanges = dynamic(["10.", "192.168.", "172.16.", "172.17.", "172.18.", "172.19.",
    "172.20.", "172.21.", "172.22.", "172.23.", "172.24.", "172.25.", "172.26.", "172.27.",
    "172.28.", "172.29.", "172.30.", "172.31.", "127.", "169.254.", "168.63."]);
let TIIPIndicators = ThreatIntelIndicators
    | where TimeGenerated > ago(90d)
    | where Confidence >= 50
    | where ObservableKey =~ "ipv4-addr:value"
    | where isnotempty(ObservableValue)
    | where not(ObservableValue has_any (PrivateRanges))
    | summarize
        ThreatType     = any(tostring(Data.indicator_types)),
        TI_Description = any(tostring(Data.description)),
        ConfScore      = max(Confidence),
        ActivityGroups = any(Tags),
        TLPLevel       = any(tostring(AdditionalFields.TLPLevel)),
        IsActive       = any(IsActive),
        Revoked        = any(Revoked)
        by TI_IP = ObservableValue;
AzureActivity
| where TimeGenerated > ago(90d)
| where isnotempty(CallerIpAddress)
| where not(CallerIpAddress has_any (PrivateRanges))
| join kind=inner TIIPIndicators on $left.CallerIpAddress == $right.TI_IP
| project
    TimeGenerated,
    Caller,
    CallerIpAddress,
    OperationNameValue,
    ActivityStatusValue,
    ResourceGroup,
    SubscriptionId,
    TI_IP,
    ThreatType,
    TI_Description,
    ConfScore,
    ActivityGroups,
    TLPLevel,
    IsActive,
    Revoked
| order by TimeGenerated desc

Explanation

This query is designed to identify potential security threats by correlating Azure management activity with threat intelligence indicators over the past 90 days. Here's a simplified breakdown of what the query does:

  1. Define Private IP Ranges: It starts by defining a list of private IP address ranges that should be excluded from the analysis.

  2. Filter Threat Intelligence Indicators:

    • It retrieves threat intelligence indicators from the last 90 days with a confidence score of 50 or higher.
    • It focuses on IPv4 addresses and excludes any that fall within the defined private IP ranges.
    • It summarizes these indicators by capturing details such as threat type, description, confidence score, activity groups, TLP level, and whether the indicator is active or revoked.
  3. Analyze Azure Activity:

    • It examines Azure activity logs from the last 90 days, specifically looking at entries with non-private IP addresses.
    • It joins these logs with the filtered threat intelligence indicators based on matching IP addresses.
  4. Output Results:

    • The query outputs a list of Azure activities that match the threat intelligence indicators, including details like the time of the activity, caller information, operation name, activity status, resource group, subscription ID, and threat intelligence details.
    • The results are ordered by the time the activity was generated, with the most recent activities listed first.

The purpose of this query is to identify historical access to the Azure management plane from IPs that were later identified as potentially malicious, even if those indicators are no longer active or have been revoked.

Details

David Alonso profile picture

David Alonso

Released: March 12, 2026

Tables

ThreatIntelIndicatorsAzureActivity

Keywords

AzureManagementPlaneThreatIntelligenceIndicatorsIPAddressActivityGroupsResourceGroupSubscriptionId

Operators

letdynamicThreatIntelIndicatorswhereago>==~isnotemptynothas_anysummarizeanytostringmaxbyAzureActivityjoinkind=inneron==projectorder bydesc

Actions