Query Details

HUNT 13 SP Risk Events Attribution 30d

Query

// Hunt     : Workload Identity - SP Risk Events Attribution and Attack Reconstruction (30d)
// Tactics  : CredentialAccess, Persistence, InitialAccess
// MITRE    : T1078.004, T1550.001
// Purpose  : Full 30-day attribution of ServicePrincipalRiskEvents. Identifies SPs with
//            the most varied risk event types (broad attack surface) and computes a
//            composite attack-breadth score. Reconstructs the exploitation picture by
//            correlating with concurrent sign-in activity and credential/privilege changes
//            to support DFIR triage and incident response.
//==========================================================================================

let LookbackDays = 30d;
let HighSeverityTypes = dynamic([
    "investigationsThreatIntelligence",
    "anomalousToken",
    "maliciousIPAddress",
    "suspiciousAPITraffic"
]);

// --- isfuzzy=true: table only exists when Identity Protection for Workload Identities is licensed ---
let _SPRiskEvents = union isfuzzy=true
    ServicePrincipalRiskEvents,
    (datatable(TimeGenerated:datetime, ServicePrincipalId:string, ServicePrincipalDisplayName:string,
               AppId:string, RiskEventType:string, RiskLevel:string, DetectedDateTime:datetime)[]);

// --- Per-SP risk event summary with composite risk scoring ---
let PerSPSummary = _SPRiskEvents
    | where TimeGenerated > ago(LookbackDays)
    | extend RiskScore = case(
        RiskLevel == "high",   3,
        RiskLevel == "medium", 2,
        RiskLevel == "low",    1,
        0)
    | extend IsHighSeverity = RiskEventType in (HighSeverityTypes)
    | summarize
        TotalEvents       = count(),
        UniqueEventTypes  = dcount(RiskEventType),
        EventTypes        = make_set(RiskEventType, 15),
        HighSeverityCount = countif(IsHighSeverity),
        MaxRiskScore      = max(RiskScore),
        TotalRiskScore    = sum(RiskScore),
        FirstEvent        = min(DetectedDateTime),
        LastEvent         = max(DetectedDateTime)
        by ServicePrincipalId, ServicePrincipalDisplayName, AppId;

// --- Sign-in activity for the same SPs over the lookback period ---
let SigninActivity = (AADServicePrincipalSignInLogs | invoke ExcludeAllowlistedIPs())
    | where TimeGenerated > ago(LookbackDays)
    | summarize
        TotalSignins      = count(),
        SuccessfulSignins = countif(ResultType == "0"),
        UniqueCountries   = dcount(Location),
        Countries         = make_set(Location, 10),
        UniqueIPs         = dcount(IPAddress),
        Resources         = make_set(ResourceDisplayName, 10),
        LastSignin        = max(TimeGenerated)
        by ServicePrincipalId;

// --- Credential and privilege changes proximate to risk events ---
let CredChanges = AuditLogs
    | where TimeGenerated > ago(LookbackDays)
    | where OperationName in (
        "Add service principal credentials",
        "Update service principal",
        "Add app role assignment to service principal",
        "Add delegated permission grant")
    | where Result =~ "success"
    | extend SPId = tostring(TargetResources[0].id)
    | summarize
        CredChangeCount = count(),
        CredOps         = make_set(OperationName, 5),
        LastCredChange  = max(TimeGenerated)
        by SPId;

PerSPSummary
| join kind=leftouter SigninActivity on ServicePrincipalId
| join kind=leftouter CredChanges on $left.ServicePrincipalId == $right.SPId
| extend MaxRiskLevel = case(
    MaxRiskScore == 3, "high",
    MaxRiskScore == 2, "medium",
    MaxRiskScore == 1, "low",
    "none")
| extend AttackBreadth = case(
    UniqueEventTypes >= 4, "Very broad — 4+ detection types triggered",
    UniqueEventTypes == 3, "Broad — 3 detection types triggered",
    UniqueEventTypes == 2, "Moderate — 2 detection types triggered",
    "Narrow — single detection type triggered")
| extend InvestigationPriority = case(
    HighSeverityCount >= 1 and coalesce(CredChangeCount, 0) > 0,    "1-Critical — Threat intel detection with credential changes",
    HighSeverityCount >= 2,                                           "2-Critical — Multiple high-severity risk event types",
    HighSeverityCount >= 1,                                           "3-High — High-severity risk event type",
    UniqueEventTypes >= 3 and coalesce(CredChangeCount, 0) > 0,     "4-High — Risk stacking with credential changes",
    UniqueEventTypes >= 3,                                            "5-High — 3+ distinct risk event types",
    UniqueEventTypes >= 2,                                            "6-Medium — Multiple risk event types",
    "7-Low — Single risk event type for baselining")
| project
    ServicePrincipalDisplayName, ServicePrincipalId, AppId,
    TotalEvents, UniqueEventTypes, EventTypes, MaxRiskLevel, TotalRiskScore,
    HighSeverityCount, AttackBreadth,
    TotalSignins, SuccessfulSignins, UniqueIPs, Countries, Resources, LastSignin,
    CredChangeCount, CredOps, LastCredChange,
    InvestigationPriority, FirstEvent, LastEvent
| order by TotalRiskScore desc, HighSeverityCount desc

Explanation

This KQL query is designed to analyze and attribute risk events related to service principals (SPs) over the past 30 days. Here's a simplified breakdown of what the query does:

  1. Purpose: The query aims to identify service principals with diverse risk event types, calculate a composite risk score, and reconstruct the attack scenario by correlating risk events with sign-in activities and credential changes. This helps in digital forensics and incident response (DFIR).

  2. Data Sources:

    • ServicePrincipalRiskEvents: Collects risk events associated with service principals.
    • AADServicePrincipalSignInLogs: Tracks sign-in activities of service principals.
    • AuditLogs: Monitors credential and privilege changes related to service principals.
  3. Risk Event Analysis:

    • Filters risk events from the last 30 days.
    • Assigns a risk score based on the severity of the risk level (high, medium, low).
    • Identifies high-severity events from a predefined list.
    • Summarizes data per service principal, including total events, unique event types, high-severity event count, and risk scores.
  4. Sign-in Activity:

    • Analyzes sign-in logs for the same service principals over the last 30 days.
    • Summarizes total sign-ins, successful sign-ins, unique countries and IPs, and resources accessed.
  5. Credential Changes:

    • Examines audit logs for credential and privilege changes related to service principals.
    • Summarizes the number of changes and the types of operations performed.
  6. Data Correlation and Scoring:

    • Joins the summarized risk events, sign-in activities, and credential changes.
    • Calculates the maximum risk level and attack breadth based on unique event types.
    • Determines an investigation priority score based on high-severity events and credential changes.
  7. Output:

    • Projects relevant information such as service principal display name, total events, risk scores, sign-in details, credential changes, and investigation priority.
    • Orders the results by total risk score and high-severity count to prioritize investigation.

This query helps security teams quickly identify and prioritize service principals that may be at risk or have been compromised, facilitating efficient incident response.