Query Details

Multiple Verified Threat Actor IP

Query

let query_frequency = 5m;
let query_period = 2d;
AADUserRiskEvents
| where TimeGenerated > ago(query_period)
| where Source == "IdentityProtection" and RiskEventType == "nationStateIP"
| 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 risk detection type indicates sign-in activity that is consistent with known IP addresses associated with nation state actors or cyber crime groups, based on Microsoft Security Intelligence (MSTIC)",
    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 == "NationStateIP"
    | 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 "estsNationStateIP"
    | 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,
//     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_V2,
    RiskLevelAggregated,
    RiskLevelDuringSignIn,
    SignInLogs_RiskState,
    HomeTenantId,
    ResourceTenantId,
    CrossTenantAccessType,
    AppId,
    ResourceIdentity,
    UniqueTokenIdentifier,
    SessionId//,
    //OriginalRequestId,
    //CorrelationId

Explanation

This KQL query is designed to analyze and correlate security risk events related to Azure Active Directory (AAD) user sign-ins that are potentially associated with nation-state actors. Here's a simplified breakdown of what the query does:

  1. Define Time Periods:

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

    • The query starts by filtering AADUserRiskEvents to include only those generated in the last 2 days (query_period) and specifically from the "IdentityProtection" source with a risk event type of "nationStateIP".
  3. Summarize Events:

    • It summarizes these events to find the first time each event was generated and keeps the latest details for each unique event ID.
  4. Filter Recent Events:

    • Further filters to include only those events that were first generated in the last 5 minutes (query_frequency).
  5. Project Relevant Fields:

    • Selects specific fields to work with, including user and event details.
  6. Process Additional Info:

    • Extracts and processes additional information from the AdditionalInfo field to create a structured data bag.
  7. Create Alternative Descriptions:

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

    • Joins the processed risk events with SecurityAlert data to enrich the information with alert details, using the event ID as a key.
  9. Extend and Coalesce Data:

    • Extends the dataset with additional fields and uses coalesce to fill in missing data with alternative values.
  10. Lookup Sign-in Logs:

    • Looks up related sign-in logs to gather more context about the user sign-ins associated with these risk events.
  11. Project Final Output:

    • Projects a comprehensive list of fields from both the risk events and sign-in logs, providing a detailed view of each incident.

In summary, this query identifies and analyzes recent AAD user risk events linked to nation-state IPs, enriches them with additional alert and sign-in information, and formats the output for further investigation or reporting.

Details

Jose Sebastián Canós profile picture

Jose Sebastián Canós

Released: October 7, 2025

Tables

AADUserRiskEventsSecurityAlertSigninLogs

Keywords

AADUserRiskEventsSecurityAlertSigninLogsUserRiskIpAddressDeviceTokenAlertAuthenticationConditionalAccessSessionAppResourceLocationNetworkProviderProductVendorEntityTechniqueTacticStatusDetailLevelTypeId

Operators

letagowheresummarizeminarg_maxbyprojectmv-applymake_bagbag_packextendstrcattouppersubstringreplace_regextostringsplitjoincoalesceiffarray_lengthtodynamicaslookuptoscalar

Actions

GitHub