Hunting Playbook: Impossible Travel in Microsoft Defender XDR.
The account signed in from Melbourne.
Minutes later, it appeared to sign in from another country.
The alert said impossible travel, but Agent Foskett knew the alert was only the start of the investigation.
The real question was whether this was a travelling user, a VPN, cloud routing, mobile network behaviour or a compromised account being used by an attacker.
Lesson overview
Learn how to investigate impossible travel by reviewing sign-in telemetry, locations, IP addresses, authentication events, device context and user behaviour across Microsoft Defender XDR and Microsoft Entra ID.
Why impossible travel matters
The impossible travel hunting workflow
Step 1 — Review recent sign-ins for the user
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
let TimeFrame = 7d;
let TargetUser = "user@contoso.com";
IdentityLogonEvents
| where Timestamp > ago(TimeFrame)
| where AccountUpn =~ TargetUser
| project Timestamp,
AccountUpn,
ActionType,
IPAddress,
Location,
DeviceName,
FailureReason
| order by Timestamp descStep 2 — Summarise sign-ins by location
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
let TimeFrame = 7d;
let TargetUser = "user@contoso.com";
IdentityLogonEvents
| where Timestamp > ago(TimeFrame)
| where AccountUpn =~ TargetUser
| summarize SignInCount = count(),
FirstSeen = min(Timestamp),
LastSeen = max(Timestamp),
IPAddresses = make_set(IPAddress, 20),
Devices = make_set(DeviceName, 20)
by AccountUpn,
Location,
ActionType
| order by LastSeen descStep 3 — Look for multiple locations in a short window
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
let TimeFrame = 24h;
IdentityLogonEvents
| where Timestamp > ago(TimeFrame)
| where ActionType has_any ("LogonSuccess", "Success")
| summarize LocationCount = dcount(Location),
Locations = make_set(Location, 10),
IPAddresses = make_set(IPAddress, 20),
FirstSeen = min(Timestamp),
LastSeen = max(Timestamp),
SignInCount = count()
by AccountUpn
| where LocationCount > 1
| order by LocationCount desc, SignInCount descStep 4 — Identify IP addresses shared across users
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
let TimeFrame = 7d;
IdentityLogonEvents
| where Timestamp > ago(TimeFrame)
| where isnotempty(IPAddress)
| summarize UserCount = dcount(AccountUpn),
Users = make_set(AccountUpn, 50),
Locations = make_set(Location, 20),
FirstSeen = min(Timestamp),
LastSeen = max(Timestamp)
by IPAddress
| where UserCount > 5
| order by UserCount descStep 5 — Build an impossible travel candidate list
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
let TimeFrame = 48h;
let MinimumLocations = 2;
IdentityLogonEvents
| where Timestamp > ago(TimeFrame)
| where ActionType has_any ("LogonSuccess", "Success")
| summarize SignInCount = count(),
LocationCount = dcount(Location),
Locations = make_set(Location, 10),
IPAddresses = make_set(IPAddress, 20),
Devices = make_set(DeviceName, 20),
FirstSeen = min(Timestamp),
LastSeen = max(Timestamp)
by AccountUpn
| where LocationCount >= MinimumLocations
| extend InvestigationNote = "Review locations, IP ownership, MFA status and user travel context"
| order by LocationCount desc, SignInCount descStep 6 — Correlate identity activity with alerts
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
let TimeFrame = 7d;
let TargetUser = "user@contoso.com";
let IdentityActivity =
IdentityLogonEvents
| where Timestamp > ago(TimeFrame)
| where AccountUpn =~ TargetUser
| project IdentityTime = Timestamp,
AccountUpn,
IPAddress,
Location,
ActionType;
let AlertActivity =
AlertEvidence
| where Timestamp > ago(TimeFrame)
| where AccountUpn =~ TargetUser or EntityValue =~ TargetUser
| project AlertTime = Timestamp,
AccountUpn,
EntityValue,
EvidenceRole,
DetectionSource;
IdentityActivity
| join kind=leftouter AlertActivity on AccountUpn
| order by IdentityTime descStep 7 — Build a clean investigation timeline
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
let TimeFrame = 7d;
let TargetUser = "user@contoso.com";
IdentityLogonEvents
| where Timestamp > ago(TimeFrame)
| where AccountUpn =~ TargetUser
| project Timestamp,
EventType = "Identity sign-in",
AccountUpn,
IPAddress,
Location,
DeviceName,
Details = strcat("Action=", ActionType, "; Failure=", FailureReason)
| order by Timestamp ascCommon false positives
Real-world investigation
Investigation checklist
Related Agent Foskett Academy lessons
Related Agent Foskett investigations
Coming next
Final thought
Hunting Playbook Impossible Travel in Microsoft Defender XDR
Agent Foskett Academy Lesson 101 teaches defenders how to hunt for impossible travel using Microsoft Defender XDR, Microsoft Entra ID identity telemetry, sign-in locations, IP addresses, authentication events and KQL investigation workflows.
Investigate impossible travel with KQL and Microsoft Defender XDR
This lesson explains how to review impossible travel alerts, validate sign-in geography, compare IP addresses, identify false positives and determine whether account compromise is likely.
Microsoft Entra ID impossible travel investigation playbook
Defenders can use IdentityLogonEvents, sign-in timelines, location summaries, IP address analysis, related alerts and Defender XDR correlation to investigate impossible travel activity.
