Query Details

HUNT 07 SP Token Abuse 7d

Query

// Hunt     : Workload Identity - SP Token Abuse Candidates (7d)
// Tactics  : CredentialAccess, LateralMovement
// MITRE    : T1528, T1550.001
// Purpose  : Surfaces SPs whose tokens were used from 3+ different IPs or 2+ countries
//            within a 60-minute window — a strong indicator of token replay/theft.
//            Groups by token identifier to show exact replay timelines.
//==========================================================================================

let PrivateRanges = dynamic(["10.", "192.168.", "172.16.", "172.17.", "172.18.",
    "172.19.", "172.20.", "172.21.", "172.22.", "172.23.", "172.24.", "172.25.",
    "172.26.", "172.27.", "172.28.", "172.29.", "172.30.", "172.31.",
    "127.", "169.254.", "168.63."]);
(AADServicePrincipalSignInLogs | invoke ExcludeAllowlistedIPs())
| where TimeGenerated > ago(7d)
| where ResultType == "0"
| where isnotempty(UniqueTokenIdentifier)
| where isnotempty(IPAddress)
| where not(IPAddress has_any (PrivateRanges))
| extend GeoInfo  = geo_info_from_ip_address(IPAddress)
| extend Country  = tostring(GeoInfo.country_iso_code)
// --- Group by token within 1-hour sliding bins ---
| summarize
    UseCount        = count(),
    UniqueIPs       = dcount(IPAddress),
    UniqueCountries = dcount(Country),
    IPList          = make_set(IPAddress, 10),
    Countries       = make_set(Country, 10),
    Resources       = make_set(ResourceDisplayName, 10),
    FirstUse        = min(TimeGenerated),
    LastUse         = max(TimeGenerated)
    by UniqueTokenIdentifier, ServicePrincipalName, ServicePrincipalId, AppId
| where UniqueIPs >= 3 or UniqueCountries >= 2
| extend MinuteSpan = datetime_diff("minute", LastUse, FirstUse)
| where MinuteSpan <= 60
| extend RiskLevel = case(
    UniqueCountries >= 3,  "Critical",
    UniqueCountries >= 2,  "High",
    UniqueIPs >= 5,        "High",
                           "Medium")
| project
    ServicePrincipalName, ServicePrincipalId, AppId,
    UniqueIPs, UniqueCountries, UseCount, MinuteSpan,
    IPList, Countries, Resources,
    FirstUse, LastUse, RiskLevel
| order by UniqueCountries desc, UniqueIPs desc

Explanation

This query is designed to identify potential security threats related to the misuse of service principal (SP) tokens in a cloud environment. Here's a simplified breakdown of what the query does:

  1. Data Source: It analyzes logs of service principal sign-ins from the past 7 days.

  2. Filtering Criteria:

    • Only considers successful sign-ins (ResultType "0").
    • Focuses on sign-ins with a unique token identifier and a valid IP address.
    • Excludes sign-ins from private IP ranges to focus on external access.
  3. Geolocation: It retrieves geographical information based on the IP addresses to determine the country of origin.

  4. Grouping and Analysis:

    • Groups sign-ins by token identifier within 1-hour time frames.
    • Counts the number of unique IP addresses and countries from which the token was used.
    • Collects lists of IP addresses, countries, and resources accessed.
  5. Suspicious Activity Detection:

    • Flags tokens used from 3 or more different IPs or 2 or more different countries within a 60-minute window as suspicious.
    • Calculates the time span between the first and last use of the token within the hour.
  6. Risk Assessment:

    • Assigns a risk level based on the number of countries and IPs involved:
      • "Critical" if used from 3 or more countries.
      • "High" if used from 2 countries or 5 or more IPs.
      • "Medium" otherwise.
  7. Output:

    • Displays relevant details such as the service principal name, ID, app ID, number of unique IPs and countries, usage count, time span, lists of IPs and countries, resources accessed, and risk level.
    • Orders the results by the number of unique countries and IPs in descending order to prioritize the most suspicious activities.

Overall, this query helps identify potential token replay or theft incidents by highlighting unusual patterns of token usage across different locations in a short time frame.

Details

David Alonso profile picture

David Alonso

Released: April 21, 2026

Tables

AADServicePrincipalSignInLogs

Keywords

ServicePrincipalSignInLogsIPAddressGeoInfoCountryTokenIdentifierResourcesAppId

Operators

letdynamicinvoke|where>ago==isnotemptynothas_anyextendgeo_info_from_ip_addresstostringsummarizecountdcountmake_setminmaxbyordatetime_diffcase>=projectorder bydesc

Actions