Detection rules › Kusto
Expired access credentials being used in Azure
This query searches for logins with an expired access credential (for example an expired cookie). It then matches the IP address from which the expired credential access occurred with the IP addresses of successful logins. If there are logins with expired credentials, but no successful logins from an IP, this might indicate an attacker has copied the authentication cookie and is re-using it on another machine.
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Credential Access | T1528 Steal Application Access Token |
Rules detecting the same action
Other rules on this platform that filter on the same API call or operation.
- Anomalous sign-in location by user account and authenticating application (Kusto)
- Anomalous Single Factor Signin (Kusto)
- Authentications of Privileged Accounts Outside of Expected Controls (Kusto)
- Azure Many Failed SignIns (Panther)
- Azure Portal sign in from another Azure Tenant (Kusto)
- Azure Service Principal Sign-In Followed by Arc Cluster Credential Access (Elastic)
- Azure SignIn via Legacy Authentication Protocol (Panther)
- Detect non-admin requesting token for admin applications (Kusto)
Rule body kusto
id: 433c3b0a-7278-4d74-b137-963ac6f9a7e7
name: Expired access credentials being used in Azure
description: |
This query searches for logins with an expired access credential (for example an expired cookie). It then matches the IP address from which the expired credential access occurred with the IP addresses of successful logins.
If there are logins with expired credentials, but no successful logins from an IP, this might indicate an attacker has copied the authentication cookie and is re-using it on another machine.
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: AzureActiveDirectory
dataTypes:
- SigninLogs
queryFrequency: 1d
queryPeriod: 7d
triggerOperator: gt
triggerThreshold: 0
tactics:
- CredentialAccess
relevantTechniques:
- T1528
query: |
// Timeframe to search for failed logins.
let timeframe=1d;
// Timeframe to look back for successful logins from the same user by IP.
let lookback=7d;
let SuspiciousSignings=(
SigninLogs
| where TimeGenerated >= ago(timeframe)
| where ResourceDisplayName contains "Windows Azure Active Directory"
// 50132 = SsoArtifactInvalidOrExpired - The session is not valid due to password expiration or recent password change.
// 50173 = FreshTokenNeeded - The provided grant has expired due to it being revoked, and a fresh auth token is needed.
// 70008 = ExpiredOrRevokedGrant - The refresh token has expired due to inactivity. The token was issued on XXX and was inactive for a certain period of time.
// 81010 = DesktopSsoAuthTokenInvalid - Seamless SSO failed because the user's Kerberos ticket has expired or is invalid.
| where ResultType in (50173, 50132, 70008, 81010)
| summarize FailedCountPerDay=count(),FailedUserAgents=make_set(UserAgent), FailedCountries=make_set(LocationDetails.countryOrRegion),FailedIps=make_set(IPAddress) by UserPrincipalName, Day=bin(TimeGenerated, 1d)
| where FailedCountPerDay >= 1
);
let SuccessLogins=(
SigninLogs
| where TimeGenerated >= ago(lookback)
| where UserPrincipalName in ((SuspiciousSignings | project UserPrincipalName))
| where ResultType == 0
| summarize count() by UserPrincipalName, IPAddress
);
SuspiciousSignings
| mv-expand FailedIp=FailedIps
| extend FailedIp=tostring(FailedIp)
| join kind=leftanti SuccessLogins on $left.FailedIp==$right.IPAddress, UserPrincipalName
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: UserPrincipalName
- entityType: IP
fieldMappings:
- identifier: Address
columnName: FailedIp
version: 1.0.0
kind: Scheduled
Stages and Predicates
Parameters
let timeframe = 1d;
let lookback = 7d;
Let binding: SuccessLogins
let SuccessLogins = (
SigninLogs
| where TimeGenerated >= ago(lookback)
| where UserPrincipalName in ((SuspiciousSignings | project UserPrincipalName))
| where ResultType == 0
| summarize count() by UserPrincipalName, IPAddress
);
Derived from lookback, SuspiciousSignings.
The stages below define let SuspiciousSignings (the rule's main pipeline source).
Stage 1: source
SigninLogs
Stage 2: where
| where TimeGenerated >= ago(timeframe)
Stage 3: where
| where ResourceDisplayName contains "Windows Azure Active Directory"
Stage 4: where
| where ResultType in (50173, 50132, 70008, 81010)
Stage 5: summarize
| summarize FailedCountPerDay=count(),FailedUserAgents=make_set(UserAgent), FailedCountries=make_set(LocationDetails.countryOrRegion),FailedIps=make_set(IPAddress) by UserPrincipalName, Day=bin(TimeGenerated, 1d)
Stage 6: where
| where FailedCountPerDay >= 1
The stages below run on SuspiciousSignings (the outer pipeline).
Stage 7: mv-expand
SuspiciousSignings
| mv-expand FailedIp=FailedIps
Stage 8: extend
| extend FailedIp=tostring(FailedIp)
Stage 9: join (negated)
| join kind=leftanti SuccessLogins on $left.FailedIp==$right.IPAddress, UserPrincipalName
Exclusions
Top-level NOT(...) conjuncts: predicates this rule actively suppresses.
| Field | Kind | Excluded values |
|---|---|---|
ResultType | eq | 0 |
Indicators
Each row is a field, operator, and value that the rule matches. The corpus column counts how many other rules in the catalog look for the same combination: high numbers point to widely-used, community-vetted indicators. Blank or 1 shows that the indicator is specific to this rule.
| Field | Kind | Values |
|---|---|---|
FailedCountPerDay | ge |
|
ResourceDisplayName | contains |
|
ResultType | in |
|
Output fields
Fields the rule emits when it matches. Chronicle authors list these in the outcome block; they appear on the detection and $risk_score drives alerting. Sentinel / Defender XDR rules build them up through project / summarize / extend stages. Sentinel maps these into alert fields via entityMappings and customDetails; Defender XDR custom detections surface them as alert fields directly.
| Field | Source |
|---|---|
Day | summarize |
FailedCountPerDay | summarize |
FailedCountries | summarize |
FailedIps | summarize |
FailedUserAgents | summarize |
UserPrincipalName | summarize |
FailedIp | extend |