Query Details

Enriched Entra Sign In Logs Suspicious Token Request

Query

// Suspicious issued refresh token outside of token broker
// Hunting query which helps to identify bounded refresh tokens without using
// Primary Refresh Token on a compliant device without involved PRT or Authentication Broker in the past 14 days

// External lookup to get list of FOCI applications
let FociClientApplications = (externaldata(client_id: string)
        [@"https://raw.githubusercontent.com/secureworks/family-of-client-ids-research/refs/heads/main/known-foci-clients.csv"] with (format="csv", ignoreFirstRecord=true)
        | project-rename FociClientId = client_id
);
union SigninLogs, AADNonInteractiveUserSignInLogs
// Optional: Filter for specific time window, user and only successful sign-ins
// | where UserPrincipalName == "<UserPrincipalName>"
//         and CreatedDateTime between ( todatetime('<StartTime>') .. todatetime('<EndTime>') )
//         and ResultType == "0"
// Filter for sign-ins to home tenant only
| where HomeTenantId == ResourceTenantId
// Enrichment of device details
| extend DeviceDetail = iff(isempty( DeviceDetail_dynamic ), todynamic(DeviceDetail_string), DeviceDetail_dynamic)
| extend DeviceId = tostring(tolower(DeviceDetail.deviceId))
| extend DeviceName = tostring(toupper(DeviceDetail.displayName))
// Enrichment of token protection details
| extend TokenProtectionStatus = iff(isempty( TokenProtectionStatusDetails_dynamic ), todynamic(TokenProtectionStatusDetails_string), TokenProtectionStatusDetails_dynamic)
| extend SignInSessionStatus = tostring(TokenProtectionStatus.signInSessionStatus)
| where IncomingTokenType == "none" and SignInSessionStatus == "bound"
// Filter for Conditional Access Device Compliance which enforces to use PRT for device identity
| extend ConditionalAccessPolicy = iff(isempty( ConditionalAccessPolicies_dynamic ), todynamic(ConditionalAccessPolicies_string), ConditionalAccessPolicies_dynamic)
| where ConditionalAccessPolicy has "RequireCompliantDevice"
    | mv-apply ConditionalAccessPolicyDeviceCompliance = parse_json(ConditionalAccessPolicy) to typeof(dynamic) on (
        where ConditionalAccessPolicyDeviceCompliance.enforcedGrantControls contains "RequireCompliantDevice" and ConditionalAccessPolicyDeviceCompliance.result == "success"
    )
| extend CaDeviceCompliance = tostring(ConditionalAccessPolicyDeviceCompliance.result)
// Lookup for FOCI client
| join kind=leftouter ( FociClientApplications ) on $left.AppId == $right.FociClientId
| extend IsFoci = iff((AppId == FociClientId), "true", "false")
// Correlation with GSA network traffic
| join kind = leftouter ( NetworkAccessTraffic
    | project TimeGenerated, TransactionId, ConnectionId, IPAddress = SourceIp, AgentVersion, UserId, DeviceId, UniqueTokenIdentifier = UniqueTokenId, InitiatingProcessName
) on UserId, DeviceId, UniqueTokenIdentifier, IPAddress
| extend IsThroughGlobalSecureAccess = iff(isnotempty(TransactionId), true, false)
// Enrichment Authentication Insights
    | extend SignInType = iff(IsInteractive == true, "Interactive", "NonInteractive")
    | extend AuthenticationMethod = tostring(parse_json(AuthenticationDetails)[0].authenticationMethod)
    | extend AuthenticationDetail = tostring(parse_json(AuthenticationDetails)[0].authenticationStepResultDetail)
    | extend AuthProcessDetails = replace_string(AuthenticationProcessingDetails, " " , "")
    | extend AuthProcessDetails = replace_string(AuthProcessDetails, "\r\n" , "")
    | parse AuthProcessDetails with * "IsClientCapable\",\"value\":\"" IsClientCapable "\"" *
    | parse AuthProcessDetails with * "IsCAEToken\",\"value\":\"" IsCaeToken "\"" *
    | parse AuthProcessDetails with * "OauthScopeInfo\",\"value\":\"" OauthScopeInfo "\"}" *
    | extend OauthScope = replace_string(OauthScopeInfo, '\\', '')
// Remove sessions where PRT or Authentication broker was involved
| join kind = anti ( SigninLogs
    // Lookback 14 days for PRT renewal
    | where (AppDisplayName == "Windows Sign-In" and IncomingTokenType == "primaryRefreshToken") or
            (AppDisplayName == "Microsoft Authentication Broker" and IncomingTokenType == "primaryRefreshToken")
    | summarize by SessionId
    ) on SessionId
| project CreatedDateTime, UserPrincipalName, IncomingTokenType, IsFoci, AppDisplayName, ResourceDisplayName, IsCaeToken, OauthScopeInfo, CaDeviceCompliance, SessionId, ClientAppUsed, SignInSessionStatus, UserAgent, InitiatingProcessName, IsThroughGlobalSecureAccess,  RiskState, RiskLevelDuringSignIn

Explanation

This KQL (Kusto Query Language) query is designed to identify suspicious refresh tokens that have been issued outside of a token broker, specifically focusing on scenarios where a Primary Refresh Token (PRT) is not used on compliant devices. The query examines sign-in logs from the past 14 days to detect such anomalies. Here's a simplified breakdown of what the query does:

  1. External Data Lookup: It retrieves a list of Family of Client IDs (FOCI) applications from an external CSV file hosted on GitHub. This list is used to identify applications that are part of the FOCI.

  2. Data Union: It combines data from two sources: SigninLogs and AADNonInteractiveUserSignInLogs. These logs contain records of user sign-ins.

  3. Filtering:

    • It filters the logs to include only sign-ins to the user's home tenant.
    • It further filters to include only those sign-ins where the token type is "none" and the session status is "bound".
  4. Device Compliance: The query checks for Conditional Access policies that require a compliant device, ensuring that the device identity is verified through a PRT.

  5. FOCI Client Identification: It checks if the application involved in the sign-in is part of the FOCI list.

  6. Network Traffic Correlation: It correlates the sign-in data with network traffic data to determine if the sign-in occurred through a Global Secure Access network.

  7. Authentication Insights: The query enriches the data with details about the authentication process, such as whether the sign-in was interactive or non-interactive, the authentication method used, and other processing details.

  8. Exclusion of PRT or Authentication Broker Involvement: It excludes sessions where a PRT or an authentication broker was involved in the sign-in process within the last 14 days.

  9. Projection: Finally, it selects specific fields to display in the results, such as the creation date and time, user principal name, application display name, compliance status, and other relevant details.

Overall, this query helps security analysts identify potentially suspicious refresh token activities that deviate from expected secure practices, particularly focusing on scenarios where compliant devices should be using PRTs.

Details

Thomas Naunheim profile picture

Thomas Naunheim

Released: March 5, 2025

Tables

SigninLogsAADNonInteractiveUserSignInLogsNetworkAccessTraffic

Keywords

DevicesIntuneUserTokenAuthenticationNetworkComplianceSessionApplication

Operators

letexternaldatawithformatproject-renameunionwhereextendiffisemptytodynamictostringtolowertoupperhasmv-applyparse_jsoncontainsjoinkindprojectparsereplace_stringsummarizeonproject

Actions