Query Details

Enriched Entra Sign In Logs Requested Token By Suspicious RT

Query

// Requested access and refresh tokens outside of Global Secure Access
// Hunting query which helps to get an overview of issued access or refresh tokens outside of Global Secure Access
// by using a bounded refresh token without IncomingTokenType

// 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
    );
// External lookup for ErrorCode description
let ErrorCodes = (externaldata(ResultType: string, Message: string)
    [@"https://raw.githubusercontent.com/f-bader/EntraID-ErrorCodes/refs/heads/main/EntraIDErrorCodes.csv"] with (format="csv", ignoreFirstRecord=true)
    | project-rename ResultErrorDescription = Message
    | project ResultType, ResultErrorDescription
    );
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 = TokenProtectionStatus.signInSessionStatus
// Lookup for FOCI client
| join kind=inner (FociClientApplications) on $left.AppId == $right.FociClientId
| extend IsFoci = iff((AppId == FociClientId), "true", "false")
| join kind = leftouter (NetworkAccessTraffic
// Correlation with GSA network traffic
    | project
        TimeGenerated,
        TransactionId,
        ConnectionId,
        IPAddress = SourceIp,
        AgentVersion,
        UserId,
        DeviceId,
        UniqueTokenIdentifier = UniqueTokenId,
        InitiatingProcessName
    )
    on UserId, DeviceId, UniqueTokenIdentifier, IPAddress
| extend TokenAcquiredThroughGsa = iff((isnotempty(ConnectionId)), "true", "false")
// Lookup for Error Code
| join kind=leftouter (ErrorCodes) on ResultType
// Filtering for specific token properties which are supicious
| where IncomingTokenType == "none" and SignInSessionStatus == "bound"
| project
    StolenTokenType = IncomingTokenType,
    StolenTokenApp = AppDisplayName,
    StolenTokenFromGsa = TokenAcquiredThroughGsa,
    StolenTokenStatus = SignInSessionStatus,
    StolenTokenUniqueTokenId = UniqueTokenIdentifier,
    StolenTokenProcessName = InitiatingProcessName,
    StolenTokenIsFoci = IsFoci,
    SessionId,
    StolenTimestamp = CreatedDateTime
// Correlation with XDR AH table of AADSignInEventsBeta to find any sign-in of affected session
| join kind = inner (
    AADSignInEventsBeta
    | extend ResultType = tostring(ErrorCode), OriginalRequestId = RequestId, DeviceId = AadDeviceId
) on SessionId
// Lookup to get error codes
| join kind=leftouter (ErrorCodes) on ResultType
// Get details of UniqueTokenIdentifier and TokenProtection for Diagnostic logs
| join kind = inner (
    union AADNonInteractiveUserSignInLogs, SigninLogs
    | extend TokenProtectionStatus = iff(isempty(TokenProtectionStatusDetails_dynamic), todynamic(TokenProtectionStatusDetails_string), TokenProtectionStatusDetails_dynamic)
    | extend SignInSessionStatus = TokenProtectionStatus.signInSessionStatus
    | join kind=leftouter (FociClientApplications) on $left.AppId == $right.FociClientId
    | extend IsFoci = iff((AppId == FociClientId), "true", "false")
    | project 
        UserPrincipalName,
        SessionId,
        CorrelationId,
        ResultType,
        OriginalRequestId,
        IPAddress,
        UniqueTokenIdentifier,
        SignInSessionStatus,
        IsFoci
) on SessionId, CorrelationId, ResultType, OriginalRequestId, IPAddress
// Remove tokens which has been issued from compliant network
| join kind = anti (NetworkAccessTraffic
    | where TimeGenerated >ago(1d)
    | project
        TimeGenerated,
        TransactionId,
        ConnectionId,
        IPAddress = SourceIp,
        AgentVersion,
        AccountObjectId = UserId,
        DeviceId,
        UniqueTokenIdentifier = UniqueTokenId,
        InitiatingProcessName
) on AccountObjectId, DeviceId, UniqueTokenIdentifier, IPAddress
| extend IsOutsideOfGsa = "true"
| project UserPrincipalName, StolenTimestamp, StolenTokenType, StolenTokenApp, StolenTokenIsFoci, StolenTokenStatus, StolenTokenProcessName, SessionId, SignInTime = Timestamp, Application, ResourceDisplayName, SignInSessionStatus, IsFoci, IsOutsideOfGsa, UniqueTokenIdentifier, ErrorCode, ResultErrorDescription
| sort by SignInTime asc

Explanation

This query is designed to identify and analyze access or refresh tokens that have been issued outside of a secure network environment, specifically outside of Global Secure Access (GSA). Here's a simplified breakdown of what the query does:

  1. External Data Sources:

    • It pulls in a list of known Family of Client IDs (FOCI) applications and error code descriptions from external CSV files hosted on GitHub.
  2. Data Union:

    • Combines data from two sources: SigninLogs and AADNonInteractiveUserSignInLogs.
  3. Filtering:

    • Filters the data to include only sign-ins to the user's home tenant.
    • Optionally, it can filter for specific users, time windows, and successful sign-ins.
  4. Data Enrichment:

    • Enriches the data with device details and token protection details.
    • Identifies if the application is a FOCI client.
  5. Network Traffic Correlation:

    • Joins the sign-in data with network traffic data to determine if the token was acquired through GSA.
  6. Error Code Lookup:

    • Joins with error code descriptions to provide more context on any errors.
  7. Suspicious Token Filtering:

    • Focuses on tokens with specific suspicious properties, such as having no incoming token type and being bound to a session.
  8. Projection:

    • Projects relevant details about the suspicious tokens, including the application, process name, and whether it is a FOCI client.
  9. Cross-Referencing with AAD Sign-In Events:

    • Correlates with additional sign-in events to find any related sessions.
  10. Final Filtering:

    • Removes tokens that were issued from a compliant network.
  11. Output:

    • Outputs a list of tokens that are identified as being issued outside of GSA, along with relevant details such as user, application, session status, and error descriptions.

The query is essentially a hunting tool to detect potentially compromised tokens that were issued outside of a secure environment, providing insights into the nature and context of these tokens for further investigation.

Details

Thomas Naunheim profile picture

Thomas Naunheim

Released: March 5, 2025

Tables

SigninLogsAADNonInteractiveUserSignInLogsNetworkAccessTrafficAADSignInEventsBeta

Keywords

DevicesUserTokensNetworkApplicationSessionErrorCodeTimestamp

Operators

letexternaldataproject-renameprojectunionwhereextendiffisemptytodynamictostringtolowertoupperjoinonisnotemptyantiagosort by

Actions