Query Details

Multiple Activity From Infrequent Country

Query

let query_frequency = 5m;
let query_period = 2d;
AADUserRiskEvents
| where TimeGenerated > ago(query_period)
| where Source == "MicrosoftCloudAppSecurity" and RiskEventType == "newCountry"
| summarize FirstTimeGenerated = min(TimeGenerated), arg_max(TimeGenerated, *) by Id
| where FirstTimeGenerated > ago(query_frequency)
| mv-apply Auxiliar = AdditionalInfo on (
    summarize AdditionalInfoBag = make_bag(bag_pack(tostring(Auxiliar["Key"]), Auxiliar["Value"]))
    )
| extend VendorOriginalId = tostring(split(tostring(AdditionalInfoBag["alertUrl"]), "/")[-1])
| project
    //TimeGenerated,
    ActivityDateTime,
    DetectedDateTime,
    Source,
    Activity,
    DetectionTimingType,
    UserDisplayName,
    UserPrincipalName,
    UserId,
    IpAddress,
    RequestId,
    CorrelationId,
    TokenIssuerType,
    RiskEventType,
    RiskDetail,
    RiskLevel,
    RiskState,
    AdditionalInfo,
    Id,
    VendorOriginalId
| 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
| join kind=leftouter (
    SecurityAlert
    | where ProviderName == "MCAS" and ProductName == "Microsoft Cloud App Security"// and AlertType == "MCAS_ALERT_ANUBIS_???"
    | 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 VendorOriginalId
| extend
    AlertLink = coalesce(AlertLink, AltAlertLink),
    ExtendedPropertiesDynamic = todynamic(ExtendedProperties)
| as _Events
| lookup kind=leftouter (
    union SigninLogs, AADNonInteractiveUserSignInLogs
    | where TimeGenerated > ago(query_period)
    //| where RiskEventTypes_V2 has "newCountry"
    | where OriginalRequestId in (toscalar(_Events | summarize make_list(RequestId)))
    | extend TimeReceived = _TimeReceived
    | summarize arg_max(TimeReceived, *) by OriginalRequestId
    | invoke UnifySignInLogs()
    | 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,
        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,
//     SignInLogs_RiskState == "dismissed" and SignInLogs_RiskDetail == "aiConfirmedSigninSafe", false,
//     SignInLogs_RiskState == "remediated" and SignInLogs_RiskDetail == "userPassedMFADrivenByRiskBasedPolicy", 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,
    RiskEventTypes_V2,
    RiskLevelAggregated,
    RiskLevelDuringSignIn,
    SignInLogs_RiskState,
    HomeTenantId,
    ResourceTenantId,
    CrossTenantAccessType,
    AppId,
    ResourceIdentity,
    UniqueTokenIdentifier,
    SessionId//,
    //OriginalRequestId,
    //CorrelationId

Explanation

This KQL query is designed to analyze user risk events related to sign-ins from new countries, using data from Azure Active Directory (AAD) and Microsoft Cloud App Security (MCAS). Here's a simplified breakdown of what the query does:

  1. Define Timeframes:

    • query_frequency is set to 5 minutes, and query_period is set to 2 days. These are used to filter events based on their timestamps.
  2. Filter Risk Events:

    • The query looks at the AADUserRiskEvents table for events generated in the last 2 days (query_period).
    • It specifically filters for events where the source is "MicrosoftCloudAppSecurity" and the risk event type is "newCountry".
  3. Summarize Events:

    • It summarizes these events to find the first time they were generated and keeps the most recent event details for each unique Id.
  4. Extract Additional Information:

    • The query extracts additional information from the AdditionalInfo field and creates a VendorOriginalId from the alertUrl.
  5. Project Relevant Fields:

    • It selects specific fields to keep for further analysis, such as user details, risk details, and event identifiers.
  6. Create Alternative Alert Link:

    • An alternative alert link is constructed for each event using the UserId.
  7. Join with Security Alerts:

    • The query performs a left outer join with the SecurityAlert table to enrich the data with alert details from MCAS, matching on VendorOriginalId.
  8. Extend and Coalesce Alert Links:

    • It ensures that the AlertLink field is populated, using the alternative link if necessary.
  9. Lookup Sign-In Logs:

    • The query looks up sign-in logs from SigninLogs and AADNonInteractiveUserSignInLogs tables for the same 2-day period.
    • It matches these logs with the events using RequestId and OriginalRequestId.
  10. Project Final Fields:

    • Finally, it selects a comprehensive set of fields from both the risk events and the sign-in logs for the final output.

In essence, this query is designed to identify and analyze risky sign-in events from new countries, 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: September 26, 2025

Tables

AADUserRiskEventsSecurityAlertSigninLogsAADNonInteractiveUserSignInLogs

Keywords

AADUserRiskEventsSecurityAlertSigninLogsAADNonInteractiveUserSignInLogsMicrosoftCloudAppSecurityMCASUserRiskAlertDeviceLocationAuthenticationConditionalAccess

Operators

letagowheresummarizeminarg_maxmv-applymake_bagbag_packtostringsplitprojectextendstrcatjoincoalescetodynamicaslookupunionintoscalarinvoke

Actions

GitHub