Query Details

Multiple Microsoft Entra Threat Intelligence

Query

let query_frequency = 5m;
let query_period = 2d;
AADUserRiskEvents
| where TimeGenerated > ago(query_period)
| where Source == "IdentityProtection" and RiskEventType == "investigationsThreatIntelligence"
| summarize FirstTimeGenerated = min(TimeGenerated), arg_max(TimeGenerated, *) by Id
| where FirstTimeGenerated > ago(query_frequency)
| project
    //TimeGenerated,
    ActivityDateTime,
    DetectedDateTime,
    Source,
    Activity,
    DetectionTimingType,
    UserDisplayName,
    UserPrincipalName,
    UserId,
    IpAddress,
    RequestId,
    CorrelationId,
    TokenIssuerType,
    RiskEventType,
    RiskDetail,
    RiskLevel,
    RiskState,
    AdditionalInfo,
    Id
| mv-apply Auxiliar = AdditionalInfo on (
    summarize AdditionalInfoBag = make_bag(bag_pack(tostring(Auxiliar["Key"]), Auxiliar["Value"]))
    )
| extend
    AltAlertLink = strcat("https://entra.microsoft.com/#blade/Microsoft_AAD_IAM/RiskDetectionsBlade/riskState~/[]/userId/", UserId, "/riskLevel/[]/daysBack/90"),// Someone wrote "90s" incorrectly in Defender XDR portal
    AltDescription = "",
    AltAlertSeverity = strcat(toupper(substring(RiskLevel, 0, 1)), substring(RiskLevel, 1)),
    AltTactics = "InitialAccess",
    AltTechniques = replace_regex(tostring(split(tostring(AdditionalInfoBag["mitreTechniques"]), ",")), @'(\")(T\d+)(\.\d+)(\")', @"\1\2\4"),
    AltSubTechniques = replace_regex(tostring(split(tostring(AdditionalInfoBag["mitreTechniques"]), ",")), @'(\,)?(\"T\d+\")', @"")
| join kind=leftouter (
    SecurityAlert
    | where ProviderName == "IPC" and ProductName == "Azure Active Directory Identity Protection"// and AlertType == ?
    | summarize arg_max(TimeGenerated, *) by VendorOriginalId
    | project
        AlertName,
        AlertSeverity,
        Description,
        AlertStatus = Status,
        Entities,
        ExtendedProperties,
        VendorName,
        ProviderName,
        ProductName,
        ProductComponentName,
        RemediationSteps,
        Tactics,
        Techniques,
        SubTechniques,
        VendorOriginalId,
        SystemAlertId,
        CompromisedEntity,
        AlertLink
    ) on $left.Id == $right.VendorOriginalId
| extend
    AlertLink = coalesce(AlertLink, AltAlertLink),
    Description = coalesce(Description, AltDescription),
    AlertSeverity = coalesce(AlertSeverity, AltAlertSeverity),
    Tactics = coalesce(Tactics, AltTactics),
    Techniques = coalesce(Techniques, iff(array_length(todynamic(AltTechniques)) == 0, "", AltTechniques)),
    SubTechniques = coalesce(SubTechniques, iff(array_length(todynamic(AltSubTechniques)) == 0, "", AltSubTechniques))
| as _Events
| lookup kind=leftouter (
    SigninLogs
    | where TimeGenerated > ago(query_period)
    //| where RiskEventTypes_V2 has "azureADThreatIntel"
    | where OriginalRequestId in (toscalar(_Events | summarize make_list(RequestId)))
    | extend TimeReceived = _TimeReceived
    | summarize arg_max(TimeReceived, *) by OriginalRequestId
    | project
        TimeGenerated,
        CreatedDateTime,
        Type,
        //UserDisplayName,
        //UserPrincipalName,
        //UserId,
        AlternateSignInName,
        SignInIdentifier,
        UserType,
        IPAddress,
        AutonomousSystemNumber,
        Location,
        NetworkLocationDetails,
        ResultType,
        ResultSignature,
        ResultDescription,
        ClientAppUsed,
        AppDisplayName,
        ResourceDisplayName,
        DeviceDetail,
        UserAgent,
        Status,
        MfaDetail,
        AuthenticationContextClassReferences,
        AuthenticationDetails,
        AuthenticationProcessingDetails,
        AuthenticationProtocol,
        AuthenticationRequirement,
        AuthenticationRequirementPolicies,
        SessionLifetimePolicies,
        //TokenIssuerType,
        IncomingTokenType,
        TokenProtectionStatusDetails,
        ConditionalAccessStatus,
        ConditionalAccessPolicies,
        SignInLogs_RiskDetail = RiskDetail,
        RiskEventTypes_V2,
        RiskLevelAggregated,
        RiskLevelDuringSignIn,
        SignInLogs_RiskState = RiskState,
        HomeTenantId,
        ResourceTenantId,
        CrossTenantAccessType,
        AppId,
        ResourceIdentity,
        UniqueTokenIdentifier,
        SessionId,
        OriginalRequestId//,
        //CorrelationId
    ) on $left.RequestId == $right.OriginalRequestId
| where case(
    //AlertStatus == "Resolved" and tostring(todynamic(ExtendedProperties)["State"]) == "Closed", false,
    //RiskState == "dismissed" and RiskDetail == "aiConfirmedSigninSafe", false,
    RiskState == "remediated" and RiskDetail == "userChangedPasswordOnPremises", false,
    true
    )
| project
    //TimeGenerated,
    ActivityDateTime,
    DetectedDateTime,
    Source,
    Activity,
    DetectionTimingType,
    UserDisplayName,
    UserPrincipalName,
    UserId,
    IpAddress,
    RequestId,
    CorrelationId,
    TokenIssuerType,
    RiskEventType,
    RiskDetail,
    RiskLevel,
    RiskState,
    AdditionalInfo,
    Id,
    AlertName,
    AlertSeverity,
    Description,
    AlertStatus,
    Entities,
    ExtendedProperties,
    VendorName,
    ProviderName,
    ProductName,
    ProductComponentName,
    RemediationSteps,
    Tactics,
    Techniques,
    SubTechniques,
    VendorOriginalId,
    SystemAlertId,
    CompromisedEntity,
    AlertLink,
    TimeGenerated,
    CreatedDateTime,
    Type,
    //UserDisplayName,
    //UserPrincipalName,
    //UserId,
    AlternateSignInName,
    SignInIdentifier,
    UserType,
    IPAddress,
    AutonomousSystemNumber,
    Location,
    NetworkLocationDetails,
    ResultType,
    ResultSignature,
    ResultDescription,
    ClientAppUsed,
    AppDisplayName,
    ResourceDisplayName,
    DeviceDetail,
    UserAgent,
    Status,
    MfaDetail,
    AuthenticationContextClassReferences,
    AuthenticationDetails,
    AuthenticationProcessingDetails,
    AuthenticationProtocol,
    AuthenticationRequirement,
    AuthenticationRequirementPolicies,
    SessionLifetimePolicies,
    //TokenIssuerType,
    IncomingTokenType,
    TokenProtectionStatusDetails,
    ConditionalAccessStatus,
    ConditionalAccessPolicies,
    SignInLogs_RiskDetail,
    RiskEventTypes_V2,
    RiskLevelAggregated,
    RiskLevelDuringSignIn,
    SignInLogs_RiskState,
    HomeTenantId,
    ResourceTenantId,
    CrossTenantAccessType,
    AppId,
    ResourceIdentity,
    UniqueTokenIdentifier,
    SessionId//,
    //OriginalRequestId,
    //CorrelationId

Explanation

This KQL query is designed to analyze and correlate user risk events from Azure Active Directory Identity Protection with security alerts and sign-in logs. Here's a simplified breakdown of what the query does:

  1. Define Timeframes:

    • query_frequency is set to 5 minutes.
    • query_period is set to 2 days.
  2. Filter Risk Events:

    • It retrieves risk events from the AADUserRiskEvents table that occurred within the last 2 days.
    • Filters these events to include only those from the "IdentityProtection" source and of type "investigationsThreatIntelligence".
  3. Summarize Events:

    • For each event, it finds the first occurrence and the most recent details, grouping by the event ID.
  4. Project Relevant Fields:

    • Selects specific fields to keep for further analysis, such as user details, risk level, and additional information.
  5. Extract Additional Information:

    • Processes the AdditionalInfo field to create a structured bag of key-value pairs.
  6. Create Alternative Fields:

    • Constructs alternative alert links, descriptions, severity, tactics, and techniques based on the risk level and additional info.
  7. Join with Security Alerts:

    • Left joins the processed risk events with the SecurityAlert table to enrich the data with alert details from Azure Active Directory Identity Protection.
  8. Coalesce Fields:

    • Ensures that if certain fields from the security alerts are missing, they are filled with the alternative values created earlier.
  9. Lookup Sign-In Logs:

    • Looks up related sign-in logs that match the request IDs from the risk events, to gather additional context about the sign-in activity.
  10. Filter Out Remediated Events:

    • Excludes events that have been remediated by a user changing their password on-premises.
  11. Final Projection:

    • Projects a comprehensive set of fields from the combined data, including user and event details, alert information, and sign-in context.

In summary, this query is used to correlate user risk events with security alerts and sign-in logs to provide a detailed view of potential security incidents involving user accounts, while also filtering out events that have been resolved through specific remediation actions.

Details

Jose Sebastián Canós profile picture

Jose Sebastián Canós

Released: October 7, 2025

Tables

AADUserRiskEventsSecurityAlertSigninLogs

Keywords

AADUserRiskEventsSecurityAlertSigninLogsUserDeviceRiskEventAlertAuthenticationNetworkLocationSession

Operators

letagowheresummarizeminarg_maxprojectmv-applymake_bagbag_packextendstrcattouppersubstringsplittostringreplace_regexjoincoalesceiffarray_lengthtodynamicaslookuptoscalarcase

Actions

GitHub