Query Details

Analytics Entra ID Protection Risk Events

Query

// This query can help you to check Entra ID Protection risk events
//
// Click "Save as function", in Parameters write in the fields:
// "timespan" "query_frequency" "90d"
// "timespan" "query_period"    "90d"
//
// If you name the function "EntraIDProtectionRiskEvents", you can check the function with queries like the following:
//
// EntraIDProtectionRiskEvents()
//
// EntraIDProtectionRiskEvents(1h, 14d)
//
// EntraIDProtectionRiskEvents(90d, 90d)
//
// let query_frequency = 5m;
// let query_period = 2d;
//let Function = (query_frequency:timespan = 90d, query_period:timespan = 90d){
let _SubstitutedDetections = toscalar(
    _GetWatchlist("AlertName-SubstitutedDetections")
    | where ProductName == "AADUserRiskEvents"
    | summarize make_list(AlertName)
);
AADUserRiskEvents
| where TimeGenerated > ago(query_period)
| summarize
    EventCount = count(),
    (FirstTimeGenerated, FirstRiskState, FirstRiskDetail) = arg_min(TimeGenerated, RiskState, RiskDetail),
    TimeGenerated = arg_max(TimeGenerated, *)
    by Id
| where TimeGenerated > ago(query_frequency)
| where not(EventCount > 1 and FirstTimeGenerated < ago(query_frequency) and FirstRiskState == RiskState and FirstRiskDetail == RiskDetail)
| extend FirstTimeGenerated = iff(FirstTimeGenerated == TimeGenerated, datetime(null), FirstTimeGenerated)
| project
    EventCount,
    FirstTimeGenerated,
    TimeGenerated,
    ActivityDateTime,
    DetectedDateTime,
    Source,
    Activity,
    DetectionTimingType,
    UserDisplayName,
    UserPrincipalName,
    UserId,
    IpAddress,
    RequestId,
    CorrelationId,
    TokenIssuerType,
    RiskEventType,
    RiskLevel,
    FirstRiskState,
    FirstRiskDetail,
    RiskState,
    RiskDetail,
    AdditionalInfo,
    Id
| mv-apply Auxiliar = AdditionalInfo on (
    summarize AdditionalInfoBag = make_bag(bag_pack(tostring(Auxiliar["Key"]), Auxiliar["Value"]))
    )
| extend
    VendorOriginalId = case(
    Source == "IdentityProtection", Id,
    Source == "MicrosoftCloudAppSecurity", tostring(split(tostring(AdditionalInfoBag["alertUrl"]), "/")[-1]),
    Source == "Defender for Endpoint", substring(tostring(split(tostring(AdditionalInfoBag["alertUrl"]), "/")[-1]), 2),
    Source == "Defender for Identity", substring(tostring(split(tostring(AdditionalInfoBag["alertUrl"]), "/")[-1]), 2),
    "Unknown"),
    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
    AltAlertName = case(
        //RiskEventType == "adminConfirmedServicePrincipalCompromised", "",
        //RiskEventType == "adminConfirmedUserCompromised", "",
        RiskEventType == "anomalousServicePrincipalActivity", "Anomalous service principal activity",
        RiskEventType == "anomalousToken", "Anomalous Token",
        RiskEventType == "anomalousUserActivity", "Anomalous user activity",
        RiskEventType == "anonymizedIPAddress", "Anonymous IP address",
        RiskEventType == "attemptedPrtAccess", "Possible attempt to access Primary Refresh Token (PRT)",
        RiskEventType == "attackerinTheMiddle", "Attacker in the Middle",
        RiskEventType == "investigationsThreatIntelligence", "Microsoft Entra threat intelligence",
        RiskEventType == "leakedCredentials", "Leaked credentials",
        //RiskEventType == "maliciousApplication", "",
        RiskEventType == "maliciousIPAddress", "Malicious IP address",// Sign-in from an IP address known to be malicious during or around the time of sign-in
        RiskEventType == "mcasFinSuspiciousFileAccess", "Mass access to sensitive files",
        RiskEventType == "mcasImpossibleTravel", "Impossible travel activity",
        RiskEventType == "mcasSuspiciousInboxManipulationRules", "Suspicious inbox manipulation rule",
        RiskEventType == "nationStateIP", "Verified threat actor IP",
        RiskEventType == "newCountry", "Activity from infrequent country",
        RiskEventType == "passwordSpray", "Password spray",
        RiskEventType == "riskyIPAddress", "Activity from anonymous IP addresses", // AlertName will change according to anonymous proxy, Tor IP address, or botnet
        RiskEventType == "suspectedKerberosSPNExposure", "Suspected Kerberos SPN exposure",
        RiskEventType == "suspiciousAdditionsSensitiveGroup", "Suspicious additions to sensitive groups",
        RiskEventType == "suspiciousAPITraffic", "Suspicious API Traffic",
        //RiskEventType == "suspiciousApplication", "",
        RiskEventType == "suspiciousBrowser", "Suspicious browser",
        RiskEventType == "suspiciousInboxForwarding", "Suspicious inbox forwarding rule",
        RiskEventType == "suspiciousSendingPatterns", "Suspicious sending patterns",
        //RiskEventType == "suspiciousSignins", "",
        RiskEventType == "tokenIssuerAnomaly", "Token issuer anomaly",
        RiskEventType == "unfamiliarFeatures", "Unfamiliar sign-in properties",
        RiskEventType == "unlikelyTravel", "Atypical travel",
        RiskEventType == "userReportedSuspiciousActivity", "User reported suspicious activity",
        "Unknown risk event"
        ),
    AltDescription = case(
        // RiskEventType == "adminConfirmedServicePrincipalCompromised", "",
        // RiskEventType == "adminConfirmedUserCompromised", "",
        // RiskEventType == "anomalousServicePrincipalActivity", "",
        RiskEventType == "anomalousToken", "Anomalous token indicates that there are abnormal characteristics in the token such as token duration and authentication from unfamiliar IP address",
        // RiskEventType == "anomalousUserActivity", "",
        RiskEventType == "anonymizedIPAddress", "Sign-in from an anonymous IP address (e.g. Tor browser, anonymizer VPNs)",
        RiskEventType == "attemptedPrtAccess", "Possible attempt to access Primary Refresh Token (PRT) has been detected. A Primary Refresh Token (PRT) is a key artifact of Azure AD authentication on Windows 10, Windows Server 2016 and later versions, iOS, and Android devices. It is a JSON Web Token (JWT) that's specially issued to Microsoft first party token brokers to enable single sign-on (SSO) across the applications used on those devices. Attackers can attempt to access this resource to move laterally into organization or perform credential theft. For more information, read the Activity profile: Midnight Blizzard credential attacks Threat Analytics report: https://security.microsoft.com/threatanalytics3/e4aad043-a273-4f88-b65e-fdfc3996ec92/analystreport?",
        // RiskEventType == "attackerinTheMiddle", "",
        // RiskEventType == "investigationsThreatIntelligence", "",
        RiskEventType == "leakedCredentials", "This risk event indicates that the user's valid credentials have been leaked",
        // RiskEventType == "maliciousApplication", "",
        RiskEventType == "maliciousIPAddress", "Sign-in from an IP address known to be malicious during or around the time of sign-in",
        RiskEventType == "mcasFinSuspiciousFileAccess", "Mass access to sensitive files",
        RiskEventType == "mcasImpossibleTravel", strcat("The user ", UserDisplayName, " (", UserPrincipalName, ") was involved in an impossible travel incident."),
        RiskEventType == "mcasSuspiciousInboxManipulationRules", "A user's inbox rules were manipulated in a suspicious manner. An attacker might have gained access to a user's inbox and is manipulating rules to delete or move messages or folders to exfiltrate data from your organization.",
        RiskEventType == "nationStateIP", "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)",
        RiskEventType == "newCountry", strcat("The user ", UserDisplayName, " (", UserPrincipalName, ") performed an activity from an infrequen country."),
        RiskEventType == "passwordSpray", "Password spray attack detected",
        RiskEventType == "riskyIPAddress", strcat("The anonymous IP address ", IpAddress, " was accessed by ", UserDisplayName, " (", UserPrincipalName, ")."),
        RiskEventType == "suspectedKerberosSPNExposure", strcat(UserDisplayName, "sent a suspected kerberos Service Principal Name and exposed several accounts."),
        RiskEventType == "suspiciousAdditionsSensitiveGroup", strcat(UserDisplayName, "added an object to a sensitive group."),
        RiskEventType == "suspiciousAPITraffic", "This detection is to identify anomalous behavior by a user principal based on MS Graph and AAD Graph activity",
        // RiskEventType == "suspiciousApplication", "",
        // RiskEventType == "suspiciousBrowser", "",
        RiskEventType == "suspiciousInboxForwarding", strcat("A suspicious inbox forwarding rule was set on the inbox of the user ", UserDisplayName, " (", UserPrincipalName, "). This may indicate that the user account is compromised, and that the mailbox is being used to exfiltrate information from your organization. "),
        // RiskEventType == "suspiciousSendingPatterns", "",
        // RiskEventType == "suspiciousSignins", "",
        // RiskEventType == "tokenIssuerAnomaly", "",
        RiskEventType == "unfamiliarFeatures", iff(
            tostring(AdditionalInfoBag["riskReasons"][0]) == "NotSet",
            "Sign-in with properties we have not seen recently for the given user",
            strcat(
                "The following properties of this sign-in are unfamiliar for the given user: ",
                strcat_array(todynamic(replace_string(tostring(AdditionalInfoBag["riskReasons"]), "Unfamiliar", "")), ", "))
            ),
        RiskEventType == "unlikelyTravel", "Sign-in from an atypical location based on the user’s recent sign-ins",
        // RiskEventType == "userReportedSuspiciousActivity", "",
        "Unknown risk event description"
        ),
    AltProviderName = case(
        Source == "IdentityProtection", "IPC",
        Source == "MicrosoftCloudAppSecurity", "MCAS",
        Source == "Defender for Endpoint", "MDATP",
        Source == "Defender for Identity", "Azure Advanced Threat Protection",
        ""
        ),
    AltProductName = case(
        Source == "IdentityProtection", "Azure Active Directory Identity Protection",
        Source == "MicrosoftCloudAppSecurity", "Microsoft Cloud App Security",
        Source == "Defender for Endpoint", "Microsoft Defender Advanced Threat Protection",
        Source == "Defender for Identity", "Azure Advanced Threat Protection",
        ""
        ),
    AltAlertSeverity = strcat(toupper(substring(RiskLevel, 0, 1)), substring(RiskLevel, 1)),
    //AltTactics = ,
    AltTechniques = replace_regex(tostring(split(tostring(AdditionalInfoBag["mitreTechniques"]), ",")), @'(\")(T\d+)(\.\d+)(\")', @"\1\2\4"),
    AltSubTechniques = replace_regex(tostring(split(tostring(AdditionalInfoBag["mitreTechniques"]), ",")), @'(\,)?(\"T\d+\")', @""),
    AccountEntity = bag_pack("UserPrincipalName", UserPrincipalName, "AadUserId", UserId, "Type", "account"),
    CloudLogonRequestEntity = bag_pack("RequestId", RequestId, "Type", "cloud-logon-request"),
    IpEntity = bag_pack("Address", IpAddress, "Type", "ip"),
    RelatedIpEntity = iff(isnotempty(tostring(AdditionalInfoBag["relatedLocation"]["clientIP"])), bag_pack("Address", tostring(AdditionalInfoBag["relatedLocation"]["clientIP"]), "Type", "ip") , dynamic(null))
| extend AltEntities = array_concat(
    iff(isnotempty(AccountEntity), pack_array(AccountEntity), dynamic(null)),
    iff(isnotempty(CloudLogonRequestEntity), pack_array(CloudLogonRequestEntity), dynamic(null)),
    iff(isnotempty(IpEntity), pack_array(IpEntity), dynamic(null)),
    iff(isnotempty(RelatedIpEntity), pack_array(RelatedIpEntity), dynamic(null))
    )
| project-away AccountEntity, CloudLogonRequestEntity, IpEntity, RelatedIpEntity
| mv-apply with_itemindex = Index_aux AltEntity = AltEntities on (
    extend AltEntity = bag_merge(bag_pack("$id", tostring(Index_aux + 2)), AltEntity)
    | summarize AltEntities = make_list(AltEntity)
    )
| join kind=leftouter (
    SecurityAlert
    | where TimeGenerated > ago(query_period)
    // | where ProviderName in ("IPC", "MCAS", "Azure Advanced Threat Protection", "MDATP")
    //      and ProductName in ("Azure Active Directory Identity Protection", "Microsoft Cloud App Security", "Azure Advanced Threat Protection", "Microsoft Defender Advanced Threat Protection")
    | where isnotempty(VendorOriginalId)
    | 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,
        ConfidenceLevel,
        ConfidenceScore,
        ExtendedLinks
    ) on VendorOriginalId
| project-away VendorOriginalId1
| mv-apply Entity = todynamic(Entities) on (
    where tostring(Entity["Type"]) == "cloud-logon-session"
    | summarize SessionIds = make_set(tostring(Entity["SessionId"]))
    )
| extend
    AlertLink = coalesce(AlertLink, AltAlertLink),
    AlertName = coalesce(AlertName, AltAlertName),
    Description = coalesce(Description, AltDescription),
    ProviderName = coalesce(ProviderName, AltProviderName),
    ProductName = coalesce(ProductName, AltProductName),
    AlertSeverity = coalesce(AlertSeverity, AltAlertSeverity),
    Techniques = coalesce(Techniques, iff(array_length(todynamic(AltTechniques)) == 0, "", AltTechniques)),
    SubTechniques = coalesce(SubTechniques, iff(array_length(todynamic(AltSubTechniques)) == 0, "", AltSubTechniques)),
    Entities = coalesce(Entities, tostring(AltEntities))
| project-away AltAlertName, AltDescription, AltProductName, AltProviderName, AltAlertSeverity, AltTechniques, AltSubTechniques, AltEntities
| as _Events
| lookup kind=leftouter (
    union SigninLogs, AADNonInteractiveUserSignInLogs
    | where TimeGenerated > ago(query_period)
    // | where RiskEventTypes in ("", "[]") or not(RiskEventTypes_V2 in ("", "[]"))
    | where OriginalRequestId in (toscalar(_Events | summarize make_list(RequestId)))
    | extend TimeReceived = _TimeReceived
    | summarize arg_max(TimeReceived, *) by OriginalRequestId
    | invoke UnifySignInLogs()
    | project
        SignInLogs_TimeGenerated = 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(
    RiskEventType in (_SubstitutedDetections), false,
    Source in ("MicrosoftCloudAppSecurity", "Defender for Endpoint", "Defender for Identity"), false,
    //AlertStatus == "Resolved" and tostring(todynamic(ExtendedProperties)["State"]) == "Closed", false,
    //RiskState == "dismissed" and RiskDetail == "aiConfirmedSigninSafe", false,
    RiskState == "remediated" and RiskDetail in ("userChangedPasswordOnPremises", "userPassedMFADrivenByRiskBasedPolicy"), false,
    RiskState == "confirmedSafe" and RiskDetail == "adminConfirmedAccountSafe", false,
    RiskState == "dismissed" and RiskDetail == "adminDismissedAllRiskForUser", false,
    true
    // Risk details
    // "adminConfirmedAccountSafe",
    // "adminConfirmedSigninCompromised",
    // "adminConfirmedSigninSafe",
    // "adminConfirmedUserCompromised",
    // "adminDismissedAllRiskForUser",
    // "adminGeneratedTemporaryPassword",
    // "aiConfirmedSigninSafe",
    // "hidden",
    // "m365DAdminDismissedDetection",
    // "unknownFutureValue",
    // "userChangedPasswordOnPremises",
    // "userPassedMFADrivenByRiskBasedPolicy",
    // "userPerformedSecuredPasswordChange",
    // "userPerformedSecuredPasswordReset"
    )
| project
    EventCount,
    FirstTimeGenerated,
    TimeGenerated,
    ActivityDateTime,
    DetectedDateTime,
    Source,
    Activity,
    DetectionTimingType,
    UserDisplayName,
    UserPrincipalName,
    UserId,
    IpAddress,
    RequestId,
    CorrelationId,
    SessionIds,
    TokenIssuerType,
    RiskEventType,
    RiskLevel,
    FirstRiskState,
    FirstRiskDetail,
    RiskState,
    RiskDetail,
    AdditionalInfo,
    Id,
    AlertName,
    AlertSeverity,
    Description,
    AlertStatus,
    Entities,
    ExtendedProperties,
    VendorName,
    ProviderName,
    ProductName,
    ProductComponentName,
    RemediationSteps,
    Tactics,
    Techniques,
    SubTechniques,
    VendorOriginalId,
    SystemAlertId,
    CompromisedEntity,
    AlertLink,
    SignInLogs_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
//};
//Function(query_frequency, query_period)

Explanation

This KQL (Kusto Query Language) script is designed to analyze risk events related to Microsoft Entra ID Protection. Here's a simplified breakdown of what the query does:

  1. Function Setup: The query is intended to be saved as a function named EntraIDProtectionRiskEvents. It takes two parameters: query_frequency and query_period, both defaulting to 90 days. These parameters define the time range for the data analysis.

  2. Watchlist Integration: It retrieves a list of substituted detection alert names from a watchlist called "AlertName-SubstitutedDetections" for filtering purposes.

  3. Data Filtering: The query filters AADUserRiskEvents to include only those generated within the specified query_period. It summarizes these events by counting occurrences and identifying the first and last time they were generated, along with their risk state and details.

  4. Event Processing: It further filters events based on query_frequency to exclude redundant or resolved events. It extends the data with additional fields like VendorOriginalId, AltAlertLink, AltAlertName, and AltDescription for better context and readability.

  5. Alert Mapping: The query joins the processed risk events with SecurityAlert data to enrich the events with alert-specific information like alert name, severity, description, and remediation steps.

  6. Sign-in Logs Lookup: It performs a lookup on SigninLogs and AADNonInteractiveUserSignInLogs to correlate risk events with sign-in activities, adding more context to the events.

  7. Final Filtering and Projection: The query applies additional filters to exclude certain resolved or dismissed risk states and projects a comprehensive set of fields for each event, including user details, risk information, alert details, and sign-in log data.

In essence, this query is a comprehensive tool for analyzing and understanding risk events related to Microsoft Entra ID Protection, providing detailed insights into potential security threats and user activities.

Details

Jose Sebastián Canós profile picture

Jose Sebastián Canós

Released: September 17, 2025

Tables

AADUserRiskEvents SecurityAlert SigninLogs AADNonInteractiveUserSignInLogs

Keywords

EntraIDProtectionRiskEventsAADUserRiskEventsUserDisplayNameUserPrincipalNameUserIdIPAddressRequestIdCorrelationIdTokenIssuerTypeRiskEventTypeRiskLevelRiskStateRiskDetailAdditionalInfoIdSecurityAlertVendorNameProviderNameProductNameProductComponentNameRemediationStepsTacticsTechniquesSubTechniquesSystemAlertIdCompromisedEntityAlertLinkSignInLogsCreatedDateTimeTypeAlternateSignInNameSignInIdentifierUserTypeIPAddressAutonomousSystemNumberLocationNetworkLocationDetailsResultTypeResultSignatureResultDescriptionClientAppUsedAppDisplayNameResourceDisplayNameDeviceDetailUserAgentStatusMfaDetailAuthenticationContextClassReferencesAuthenticationDetailsAuthenticationProcessingDetailsAuthenticationProtocolAuthenticationRequirementAuthenticationRequirementPoliciesSessionLifetimePoliciesIncomingTokenTypeTokenProtectionStatusDetailsConditionalAccessStatusConditionalAccessPoliciesRiskEventTypesV2RiskLevelAggregatedRiskLevelDuringSignInHomeTenantIdResourceTenantIdCrossTenantAccessTypeAppIdResourceIdentityUniqueTokenIdentifierSessionId

Operators

lettoscalar_GetWatchlistwheresummarizemake_listagocountarg_minarg_maxbynotandextendiffdatetimeprojectmv-applymake_bagbag_packcaseSourcestrcatsplittostringsubstringreplace_stringarray_lengthtodynamictoupperbag_mergearray_concatpack_arraydynamicjoinkindleftoutercoalesceproject-awayunioninvokeisnotemptysummarizemake_setonaslookupwith_itemindexmake_listsummarizemake_setsummarizearg_maxiffproject-awayproject

Actions