Query Details

Enriched Entra Sign In Logs Gsa Enforcement By Ca Policy

Query

// Global Secure Access Enforcement by Conditional Access Policy
// Hunting query which helps to identify and analyze sign-in requests outside of Global Secure Access
// with details about exclusion on Compliant Network conditions in Conditional Access

// Definition of Conditional Access Policy which blocks access outside of Compliant Network GSA
let CaPolicyBlockedOutsideGsa = '<DisplayNameOfConditionalAccessPolicyForBlockedOutsideOfGSA>';
// Definition of excluded Cloud Apps from compliant network CA, examples covers recommendation by Microsoft for exclusions
let ExplicitlyGsaExcludedCloudAppIds = dynamic([
    "372140e0-b3b7-4226-8ef9-d57986796201", // Azure Windows VM Sign-In
    "0000000a-0000-0000-c000-000000000000", // Microsoft Intune
    "d4ebce55-015a-49b5-a083-c84d1797ae8c"  // Microsoft Intune Enrollment
]);
union SigninLogs, AADNonInteractiveUserSignInLogs
// Filter for sign-ins to home tenant only
| where HomeTenantId == ResourceTenantId
// Optional: Filter for specific time window and user
// | where UserPrincipalName == "<UserPrincipalName>"
//         and CreatedDateTime between ( todatetime('<StartTime>') .. todatetime('<EndTime>') )
// Expand device details
| extend DeviceDetail = iff(isempty( DeviceDetail_dynamic ), todynamic(DeviceDetail_string), DeviceDetail_dynamic)
| extend DeviceId = tostring(tolower(DeviceDetail.deviceId))
| extend DeviceName = tostring(tolower(DeviceDetail.displayName))
// Expand Token Protection Status details
| extend TokenProtectionStatus = iff(isempty( TokenProtectionStatusDetails_dynamic ), todynamic(TokenProtectionStatusDetails_string), TokenProtectionStatusDetails_dynamic)
| extend SignInSessionStatus = tostring(TokenProtectionStatus.signInSessionStatus)
// Correlate token acquisition with NetworkAccessTraffic logs from GSA
| join kind = leftouter ( NetworkAccessTraffic
    | project TimeGenerated, TransactionId, ConnectionId, IPAddress = SourceIp, AgentVersion, UserId, DeviceId, UniqueTokenIdentifier = UniqueTokenId, InitiatingProcessName
) on UserId, DeviceId, UniqueTokenIdentifier, IPAddress
// Workaround for missing valid property for IsThroughGlobalSecureAccess in sign-in logs
| extend IsThroughGlobalSecureAccess = iff(isnotempty(TransactionId), true, false)
// Summarize results for simplified view
| extend ResultType = toint(ResultType)
| project-reorder CreatedDateTime, SessionId, IncomingTokenType, TokenIssuerType, SignInSessionStatus, UniqueTokenIdentifier, AppDisplayName, ResourceDisplayName, IsThroughGlobalSecureAccess, InitiatingProcessName
// Correlation with events from XDR AH AADSignInEventsBeta to get Conditional Access details
| join kind=innerunique (
    AADSignInEventsBeta
) on $left.SessionId == $right.SessionId, $left.CorrelationId == $right.CorrelationId, $left.OriginalRequestId == $right.RequestId, $left.ResultType == $right.ErrorCode, $left.IPAddress == $right.IPAddress, $left.UserId == $right.AccountObjectId
// Enrichment of GSA Insights 
| mv-apply ConditionalAccessPolicyGsa = parse_json(ConditionalAccessPolicies) to typeof(dynamic) on (
    where ConditionalAccessPolicyGsa.displayName startswith (CaPolicyBlockedOutsideGsa)
)
    | extend IsGsaEnforced =  iff(
        (
            parse_json(ConditionalAccessPolicyGsa)["result"] == 'notApplied' and
            parse_json(ConditionalAccessPolicyGsa)["excludeRulesSatisfied"] has 'locationId' and
            parse_json(ConditionalAccessPolicyGsa)["enforcedGrantControls"][0] == 'Block'
        ), true, false)
    | extend GsaEnforcedResourceScope =  case(
        (
            (AppId in~ (ExplicitlyGsaExcludedCloudAppIds) or ResourceIdentity in~ (ExplicitlyGsaExcludedCloudAppIds)) and
            parse_json(ConditionalAccessPolicyGsa)["excludeRulesSatisfied"] has 'appId')
        , "ExplicitlyExcluded",      
            (parse_json(ConditionalAccessPolicyGsa)["includeRulesSatisfied"] has AppId)
        , "ExplicitlyIncluded",                  
            parse_json(ConditionalAccessPolicyGsa)["includeRulesSatisfied"] has 'allApps' and 
            parse_json(ConditionalAccessPolicyGsa)["excludeRulesSatisfied"] !has 'appId'
        , "AllAppsIncluded",            
            (AppId !in~ (ExplicitlyGsaExcludedCloudAppIds) or ResourceIdentity !in~ (ExplicitlyGsaExcludedCloudAppIds)) and
            parse_json(ConditionalAccessPolicyGsa)["excludeRulesSatisfied"] has 'appId'
        , "MicrosoftExcluded", "Unknown"
        )
    | extend GsaEnforcedUserScope = case(
        (
            parse_json(ConditionalAccessPolicyGsa)["excludeRulesSatisfied"] has 'userId')
        , "ExplicitlyExcluded",
            (parse_json(ConditionalAccessPolicyGsa)["includeRulesSatisfied"] has 'userId')
        , "ExplicitlyIncluded",                  
            parse_json(ConditionalAccessPolicyGsa)["includeRulesSatisfied"] has 'allUsers' and 
            parse_json(ConditionalAccessPolicyGsa)["excludeRulesSatisfied"] !has 'userId'
        , "AllUsersIncluded",            
            parse_json(ConditionalAccessPolicyGsa)["includeRulesSatisfied"] !has 'allUsers' and 
            (parse_json(ConditionalAccessPolicyGsa)["includeRulesSatisfied"] !has 'userId') and
            parse_json(ConditionalAccessPolicyGsa)["excludeRulesSatisfied"] !has 'userId'
        , "UserNotIncluded", "Unknown"
        )
| project CreatedDateTime, UserPrincipalName, IncomingTokenType, SignInSessionStatus, AppDisplayName, ResourceDisplayName, InitiatingProcessName, IsThroughGlobalSecureAccess, ConnectionId, IsGsaEnforced, GsaEnforcedUserScope, GsaEnforcedResourceScope, ConditionalAccessPolicyGsa
| sort by CreatedDateTime desc
// Filter for sign-ins which are not going trough GSA and have not been enforced to use GSA
| where IsThroughGlobalSecureAccess == false and IsGsaEnforced == false

Explanation

This query is designed to identify and analyze sign-in requests that occur outside of a Global Secure Access (GSA) environment, focusing on situations where Conditional Access policies are supposed to enforce GSA usage but do not. Here's a simplified breakdown of what the query does:

  1. Define Key Variables:

    • It sets up a variable for a specific Conditional Access Policy that blocks access outside of GSA.
    • It also defines a list of cloud applications that are explicitly excluded from this policy.
  2. Data Sources:

    • The query pulls data from sign-in logs and non-interactive user sign-in logs.
    • It filters these logs to include only sign-ins to the home tenant.
  3. Data Processing:

    • It expands details about the device and token protection status for each sign-in attempt.
    • It attempts to correlate these sign-ins with network traffic logs to determine if the sign-in went through GSA.
  4. Conditional Access Policy Analysis:

    • The query joins the sign-in data with Conditional Access event data to get more details about the policies applied.
    • It checks if the Conditional Access Policy meant to enforce GSA was applied or not.
  5. Enrichment and Classification:

    • It enriches the data with insights about whether the sign-in was supposed to be enforced by GSA.
    • It classifies the sign-ins based on whether they were explicitly included or excluded from GSA enforcement, both for resources and users.
  6. Filtering and Output:

    • The final output includes sign-ins that did not go through GSA and were not enforced to use GSA, sorted by the time of the sign-in.
    • The results include details like the user, application, and whether GSA enforcement was expected.

This query helps security analysts understand and investigate instances where sign-ins occur outside of the expected secure environment, potentially highlighting gaps in policy enforcement or configuration.

Details

Thomas Naunheim profile picture

Thomas Naunheim

Released: March 5, 2025

Tables

SigninLogsAADNonInteractiveUserSignInLogsNetworkAccessTrafficAADSignInEventsBeta

Keywords

GlobalSecureAccessDevicesIntuneUserNetworkAccessTrafficConditionalAccessPolicyMicrosoftAzureTokenProtectionStatusSessionIdResourceDisplayNameAppDisplayNameConnectionIdTransactionIdAgentVersionUserIdDeviceIdUniqueTokenIdentifierInitiatingProcessNameSignInSessionStatusTokenIssuerTypeIncomingTokenType

Operators

letunionwhereextendiffisemptytodynamictostringtolowerjoinkindprojectproject-reordermv-applyparse_jsontointcasein~has!hassortdesc

Actions