Query Details

Token Replay from workload identity with privileges in Microsoft Entra or Microsoft 365 (WorkloadIdentityInfo)

Token Replay From Workload Identity With Privileges In Microsoft 365 Workload Identity Info

Query

let azure_ranges = externaldata(changeNumber: string, cloud: string, values: dynamic)
["https://raw.githubusercontent.com/microsoft/mstic/master/PublicFeeds/MSFTIPRanges/ServiceTags_Public.json"] with(format='multijson')
| mv-expand values
| mv-expand values.properties.addressPrefixes
| mv-expand values_properties_addressPrefixes
| summarize by tostring(values_properties_addressPrefixes)
| extend isipv4 = parse_ipv4(values_properties_addressPrefixes)
| extend isipv6 = parse_ipv6(values_properties_addressPrefixes)
| extend ip_type = case(isnotnull(isipv4), "v4", "v6")
| summarize make_list(values_properties_addressPrefixes) by ip_type;
MicrosoftGraphActivityLogs
| project TimeGenerated, RequestId, ApiVersion, RequestMethod, ResponseStatusCode, ActivityIpAddress = IPAddress, UserAgent, RequestUri, Roles, AppId, Wids, SignInActivityId, ApplicationId = AppId, Scopes, Location
| join kind=inner (union AADServicePrincipalSignInLogs, AADManagedIdentitySignInLogs
  | project ConditionalAccessPolicies, ConditionalAccessStatus, ServicePrincipalCredentialKeyId, SignInIpAddress = IPAddress, UniqueTokenIdentifier, Type
  // Correlation between Activity and Sign-in based on Unique Token Identifier
) on $left.SignInActivityId == $right.UniqueTokenIdentifier
// AADManagedIdentitySignInLogs does not include SignInIpAddress
| where ActivityIpAddress != SignInIpAddress and isnotempty(SignInIpAddress)
| extend isipv4 = parse_ipv4(ActivityIpAddress)
| extend ip_type = case(isnotnull(isipv4), "v4", "v6")
| join kind=fullouter (azure_ranges) on ip_type
| extend ipv6_match = ipv6_is_in_any_range(ActivityIpAddress, list_values_properties_addressPrefixes)
| extend ipv4_match = ipv4_is_in_any_range(ActivityIpAddress, list_values_properties_addressPrefixes)
| extend IpAddressType = iff(ipv4_match or ipv6_match, "Azure Public IP", "None Azure IP")
| where isnotempty(ApplicationId)
| join kind=leftouter(
    PrivilegedWorkloadIdentityInfo
    | project
        WorkloadIdentityName,
        WorkloadIdentityType,
        ApplicationObjectId,
        ServicePrincipalObjectId,
        ApplicationId,
        IsFirstPartyApp,
        EntraIdRoles,
        AppRolePermissions,
        WorkloadIdClassification = EnterpriseAccessModelTiering
    )
    on ApplicationId
| extend Severity = iff(WorkloadIdClassification contains "ControlPlane", "High", "Medium")

Explanation

This query detects a token replay attack on Microsoft Graph API. It checks if a token has been used from a different IP address than during the sign-in process. The severity is set to medium, but it can be high if the workload identity has privileges on the Control Plane. The query frequency and period are both set to 1 hour. The query retrieves Azure IP ranges and then joins the MicrosoftGraphActivityLogs with AADServicePrincipalSignInLogs and AADManagedIdentitySignInLogs to correlate activity and sign-in based on a unique token identifier. It filters out activity with different IP addresses and joins with PrivilegedWorkloadIdentityInfo to get additional information. The query also includes incident configuration and alert details override.

Details

Thomas Naunheim profile picture

Thomas Naunheim

Released: November 20, 2023

Tables

MicrosoftGraphActivityLogs AADServicePrincipalSignInLogs AADManagedIdentitySignInLogs PrivilegedWorkloadIdentityInfo

Keywords

DevicesIntuneUserMicrosoft Graph APIIP AddressSign-inAzure Public IPPrivileged Workload IdentityControl PlaneMicrosoft EntraMicrosoft 365

Operators

letexternaldatawithmv-expandsummarizeextendcasejoinonwhereiffcontainsproject

Severity

Medium

Tactics

CredentialAccess

MITRE Techniques

Frequency: 1h

Period: 1h

Actions

GitHub