hunt-2025-037 v1.0 by Paul Newton

Spike in invalid token usage (50173)

Platform

Entra

Data Sources

AADNonInteractiveUserSignInLogs MicrosoftGraphActivityLogs

MITRE ATT&CK

Tactics

Defense Evasion

Techniques

Threat Actors

APT29/CozyBear/MidnightBlizzard

Tags

#entra #cloud #identity #oauth

Hunt Hypothesis

Threat Actors will use token theft as a means of initial access, privilege escalation and persistence. Poorly secured tokens by third parties can increase the risk of token theft and replay.

Spike in invalid token usage (50173)

Analytic #1

This analytic looks for cases where there is a spike in invalid token usage, with a short time span. This could be indicative of mass token replay against accounts. This analytic is reliant on the error code 50173 "The provided grant has expired due to it being revoked, a fresh auth token is needed.....". This could indicate exfiltration of tokens for replay where some of the tokens have expired or are no longer valid.

Detection Queries

KQL
let LookbackPeriod = 7d;
let BinSize = 5m;  // Tighter window to catch bursts
let MinEvents = 10;
AADNonInteractiveUserSignInLogs
| where TimeGenerated > ago(LookbackPeriod)
| where ResultType == 50173
| summarize 
    Count = count(),
    UniqueIPs = dcount(IPAddress),
    IPAddresses = make_set(IPAddress),
    UniqueTokens = dcount(UniqueTokenIdentifier),
    UniqueUsers = dcount(UserPrincipalName),
    Users = make_set(UserPrincipalName),
    Locations = make_set(Location),
    UniqueLocations = dcount(Location),
    UserAgents = make_set(UserAgent),
    ResourcesAccessed = make_set(ResourceDisplayName),
    IncomingTokenTypes = make_set(IncomingTokenType),
    FirstSeen = min(TimeGenerated),
    LastSeen = max(TimeGenerated)
    by bin(TimeGenerated, BinSize), AppDisplayName, AppId
| where Count >= MinEvents
| extend 
    TimeWindow = strcat(format_datetime(FirstSeen, 'HH:mm:ss'), " - ", format_datetime(LastSeen, 'HH:mm:ss')),
    IPLocationRatio = round(todouble(UniqueIPs) / todouble(UniqueLocations), 2),
    TokensPerIP = round(todouble(UniqueTokens) / todouble(UniqueIPs), 2)
| project 
    TimeGenerated,
    TimeWindow,
    AppDisplayName,
    AppId,
    Count,
    UniqueIPs,
    UniqueTokens,
    UniqueUsers,
    UniqueLocations,
    IPLocationRatio,
    TokensPerIP,
    IPAddresses,
    Users,
    Locations,
    UserAgents,
    ResourcesAccessed,
    IncomingTokenTypes
| order by Count desc

Triage Steps

  1. Review the time window and count of failed authentications - high volume in short time indicates mass replay
  2. Check if there were recent password changes or token revocations for the affected users
  3. Investigate the application generating the invalid token errors
  4. Review if multiple users are affected or just a single account
  5. Analyze the UserAgent to identify if automated tooling is being used
  6. Check the IP addresses and locations to identify potential attacker infrastructure
  7. Look for successful authentications before or after the spike that may indicate compromise
  8. If confirmed mass replay attempt, reset credentials and revoke application access

True Positive Example

Example 1

Log Entry:
{
  "TimeGenerated": "06/12/2025, 19:55:00.000",
  "TimeWindow": "19:55:02 - 19:57:07",
  "AppDisplayName": "TruffleHog Test App",
  "AppId": "c32a4v3a-c5bd-4d70-b388-091430657d83",
  "Count": 86,
  "UniqueIPs": 1,
  "UniqueTokens": 86,
  "UniqueUsers": 1,
  "UniqueLocations": 1,
  "IPLocationRatio": 1,
  "TokensPerIP": 86,
  "IPAddresses": [
    "79.135.105.18"
  ],
  "Users": [
    "hunter1@REDACTED.onmicrosoft.com"
  ],
  "Locations": [
    "AZ"
  ],
  "UserAgents": [
    "python-requests/2.31.0"
  ],
  "ResourcesAccessed": [
    "Microsoft Graph"
  ],
  "IncomingTokenTypes": [
    "refreshToken"
  ],
  "UserDisplayName": "Hunter",
  "ResourceDisplayName": "Microsoft Graph",
  "IPAddress": "79.135.105.18",
  "Location": "AZ",
  "Browser": "Python Requests 2.31",
  "DeviceId": "",
  "IncomingTokenType": "refreshToken",
  "SessionStatus": "unbound"
}
Analysis:

Mass replay attempt detected - 86 invalid tokens attempted in a 2-minute window from a single IP using Python automation. This pattern indicates an attacker attempting to use a large cache of stolen tokens, many of which have been revoked or expired.

Example 2

Log Entry:
{
  "TimeGenerated": "06/12/2025, 19:50:00.000",
  "TimeWindow": "19:54:24 - 19:54:57",
  "AppDisplayName": "TruffleHog Test App",
  "AppId": "ca2a4f3a-c6bd-4b70-b888-0914b0657d83",
  "Count": 13,
  "UniqueIPs": 1,
  "UniqueTokens": 13,
  "UniqueUsers": 1,
  "UniqueLocations": 1,
  "IPLocationRatio": 1,
  "TokensPerIP": 13,
  "IPAddresses": [
    "79.135.105.18"
  ],
  "Users": [
    "hunter1@REDACTED.onmicrosoft.com"
  ],
  "Locations": [
    "AZ"
  ],
  "UserAgents": [
    "python-requests/2.31.0"
  ],
  "ResourcesAccessed": [
    "Microsoft Graph"
  ],
  "IncomingTokenTypes": [
    "refreshToken"
  ],
  "UserDisplayName": "Hunter",
  "ResourceDisplayName": "Microsoft Graph",
  "IPAddress": "79.135.105.18",
  "Location": "AZ",
  "Browser": "Python Requests 2.31",
  "DeviceId": "",
  "IncomingTokenType": "refreshToken",
  "SessionStatus": "unbound"
}
Analysis:

Smaller burst of 13 invalid token attempts in 33 seconds. While lower volume than the previous spike, the rapid-fire automation pattern is consistent with credential stuffing or token replay attacks from compromised application data.