Query Details

Multiple Suspicious API Traffic

Query

let query_frequency = 5m;
let query_period = 2d;
AADUserRiskEvents
| where TimeGenerated > ago(query_period)
| where Source == "IdentityProtection" and RiskEventType == "suspiciousAPITraffic"
| 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 = "This detection is to identify anomalous behavior by a user principal based on MS Graph and AAD Graph activity",
    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 == "SuspiciousAPITraffic"
    | 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 (
    AADNonInteractiveUserSignInLogs
    | where TimeGenerated > ago(query_period)
    //| where RiskEventTypes_V2 has "suspiciousAPITraffic"
    | 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 (Kusto Query Language) query is designed to analyze suspicious API traffic events related to Azure Active Directory (AAD) user risk events. Here's a simplified breakdown of what the query does:

  1. Set Parameters:

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

    • It retrieves events from the AADUserRiskEvents table where the event was generated within the last 2 days and is sourced from "IdentityProtection" with a risk event type of "suspiciousAPITraffic".
  3. Summarize Events:

    • It summarizes these events to find the first time each event was generated and the most recent event for each unique Id.
  4. Filter Recent Events:

    • It further filters to include only those events that first occurred within the last 5 minutes.
  5. Project Relevant Columns:

    • It selects specific columns to include in the output, such as user details, risk details, and additional information.
  6. Process Additional Information:

    • It processes the AdditionalInfo field to create a structured bag of key-value pairs.
  7. Create Alternative Alert Details:

    • It constructs alternative alert details, such as a link to the alert, description, severity, tactics, and techniques, using the processed additional information.
  8. Join with Security Alerts:

    • It performs a left outer join with the SecurityAlert table to enrich the data with additional alert details if available.
  9. Extend and Coalesce Data:

    • It extends the data with coalesced values, ensuring that if certain alert details are missing, alternative values are used.
  10. Lookup Sign-In Logs:

    • It performs a lookup with AADNonInteractiveUserSignInLogs to correlate the suspicious API traffic events with non-interactive sign-in logs.
  11. Project Final Output:

    • Finally, it projects a comprehensive set of columns from both the risk events and sign-in logs for further analysis or reporting.

Overall, this query is designed to identify and analyze suspicious API traffic events related to user accounts in Azure Active Directory, enrich them with additional security alert information, and correlate them with sign-in logs to provide a detailed view of potential security incidents.

Details

Jose Sebastián Canós profile picture

Jose Sebastián Canós

Released: October 7, 2025

Tables

AADUserRiskEventsSecurityAlertAADNonInteractiveUserSignInLogs

Keywords

AADUserRiskEventsSecurityAlertAADNonInteractiveUserSignInLogsDevicesUser

Operators

letagowheresummarizeminarg_maxbyprojectmv-applymake_bagbag_packextendstrcattouppersubstringsplittostringreplace_regexjoincoalesceiffarray_lengthtodynamicaslookuptoscalarmake_list

Actions

GitHub