Skip to content
HackInvasionCybersecurity Knowledge Hub

Impossible Travel: Hunting Compromised Identities With Geospatial Logon Velocity

Dark illustration of a glowing world map with blue travel arcs connecting distant cities, symbolizing impossible-travel logon detection across geographies

At 09:12 the user signed in from Toronto. At 09:47 the same user signed in from Lagos. Commercial flight time between the two cities: roughly twelve hours. The user does not own a supersonic jet — but the attacker owns their session cookie, and that is all this signal is really measuring. Impossible travel is not about travel at all. It is about physics: two authentications happened too far apart, too close together, for one human to have produced both.

Session-token theft, credential stuffing, and phishing kits all produce this artifact. The attacker signs in with stolen credentials or a replayed token from infrastructure on another continent, while the legitimate user keeps signing in normally from home. Neither logon alone looks malicious — the anomaly only exists in the relationship between the two. That makes impossible travel a genuinely threat-hunting-shaped problem: no signature fires, but the geometry does not add up.

The hypothesis

An identity is under active attack when two successful logons for the same account originate from geographies separated by a distance that cannot be covered in the elapsed time — a velocity no legitimate user can achieve, most commonly caused by session-cookie theft or credential replay.

Data you'll need

  • Microsoft Sentinel / Defender: IdentityLogonEvents (Defender for Identity / Entra logons with Location and LocationDetails), enriched with IP geolocation.
  • Splunk: Entra / Azure AD sign-in logs (sourcetype="azure:aad:signin" or your connector's equivalent) with location and ipAddress fields; iplocation for enrichment when fields are missing.
  • Context data: known VPN/proxy egress ranges (they are the number-one source of fake "travel"), and the user's normal home geography.

Hunting with KQL

This query computes the velocity between each pair of consecutive logons per user and flags anything faster than a commercial airliner:

// Impossible-travel hunt: geospatial logon velocity
IdentityLogonEvents
| where TimeGenerated > ago(14d)
| where LogonResult == "Success"
| where isnotempty(Location) and isnotempty(LocationDetails)
| extend Lat = todouble(LocationDetails.Latitude),
         Lon = todouble(LocationDetails.Longitude)
| where isnotnull(Lat) and isnotnull(Lon)
| order by AccountUpn asc, TimeGenerated asc
| extend PrevTime = prev(TimeGenerated, 1),
         PrevLat  = prev(Lat, 1),
         PrevLon  = prev(Lon, 1),
         PrevIP   = prev(IPAddress, 1)
| where AccountUpn == prev(AccountUpn, 1)      // same user, consecutive logons
| extend DeltaMinutes = datetime_diff("minute", TimeGenerated, PrevTime)
| extend DistanceKm   = geo_distance_2points(Lon, Lat, PrevLon, PrevLat) / 1000
| extend VelocityKph  = iff(DeltaMinutes > 0, DistanceKm / (DeltaMinutes / 60.0), 0)
| where VelocityKph > 1000                    // faster than any real itinerary
| where DistanceKm > 500                      // ignore same-city GPS jitter
| project TimeGenerated, AccountUpn, IPAddress, Location,
          PrevIP, PrevTime, DistanceKm, DeltaMinutes, VelocityKph,
          Application, DeviceName
| order by VelocityKph desc

What this does: for each user it walks logons in chronological order, measures the great-circle distance (geo_distance_2points returns meters, hence the / 1000) between consecutive logons, and divides by the elapsed time. The 1,000 km/h bar is deliberately above any plausible commercial flight — it selects for physics violations, not business trips. The 500 km floor suppresses same-city geolocation jitter, which is the largest noise source in IP-based geo data.

Example true-positive row:

AccountUpna.chen@corp.local
PrevTime → TimeGenerated09:12 → 09:47 (35 min apart)
PrevIP → IPAddress172.16.8.40 (Toronto) → 197.210.55.19 (Lagos)
DistanceKm / VelocityKph9,200 km at ~15,800 km/h
Application / DeviceNameOffice 365 / unknown device

The user did not commute to Lagos for their 09:47 email check. Someone else holds a valid session.

Tightening the net: add device-trust and MFA context

A velocity hit is far more damning when the "traveling" logon comes from an unknown device with a fresh session. Extend the project with LogonType, DeviceDetail, and MFA outcome fields if your logon stream carries them — a logon from Lagos with no MFA challenge on a brand-new session is the classic fingerprint of a replayed session cookie (pass-the-cookie), because cookie replay skips the MFA step entirely.

Hunting with Splunk

A summary-based equivalent against Entra sign-in logs — it finds accounts with successful logons from multiple countries in a short window:

index=azuread sourcetype="azure:aad:signin" result=0 earliest=-14d
| eval ip=coalesce(ipAddress, IPAddress)
| iplocation ip
| stats earliest(_time) as first, latest(_time) as last,
        dc(Country) as countries, values(Country) as country_list,
        values(City) as city_list, values(ip) as ips,
        dc(userPrincipalName) as u
        by userPrincipalName
| where countries > 1 AND (last - first) < 3600
| eval window_min = round((last - first)/60, 1)
| sort - countries, window_min
| table userPrincipalName, first, last, window_min, countries,
        country_list, city_list, ips

What this does: enriches each sign-in with country/city via iplocation, then rolls up per user and keeps only accounts that hit more than one country within a single hour. It is coarser than the KQL velocity math, but it is fast, easy to schedule, and excellent for a first pass across the tenant. Note the private-IP caveat: iplocation cannot geolocate RFC-1918 addresses, so pair this with a filter on public IPs if your sign-in stream mixes on-prem and cloud events.

Example hit: one row for a.chen@corp.local with country_list = Canada, Nigeria, window_min = 35, and two distinct public IPs — the same conclusion, reached with less geometry.

Validating the hit

  1. Rule out the boring explanations first. Check both IPs against your VPN egress ranges, cloud-access proxies (Zscaler, Cloudflare), and the user's mobile carrier — corporate egress is the single biggest source of fake travel.
  2. Inspect the suspicious session. Was MFA challenged? A successful logon on a new device/session with no MFA prompt strongly suggests a stolen session token rather than a stolen password.
  3. Look for post-logon abuse. Review what the Lagos session actually did — mailbox rules created, OAuth app grants, data downloads, or password-reset attempts are the attacker's to-do list, and they tell you the blast radius.
  4. Ask the human. A 30-second check with the user ("Were you in Lagos at 09:47?") is the fastest ground truth available, and it works even when the telemetry is ambiguous.

Tuning out false positives

  • Corporate VPN / SASE egress — the Lagos IP may be the company's own egress node; maintain an allowlist of known egress ranges and join it into the query.
  • Traveling executives — a flight from Toronto to London is not impossible travel; the velocity math already tolerates this, but short-hop business travel across nearby borders can still cluster. Baseline frequent travelers or widen the window.
  • Mobile carrier NAT — some carriers egress through another country; these appear as low-velocity, low-confidence hits — combine with device familiarity to dismiss.
  • Satellite internet and remote offices — users on satellite links or behind regional office NATs geolocate oddly; the 500 km floor in the KQL handles most of this.

What to do next

For a validated hit, act on the session, not just the password: revoke all active sessions and refresh tokens, force a password reset, and require re-authentication on a compliant device — because a password change alone does not kill a stolen session cookie. Then investigate the token-theft vector: check for recent infostealer activity on the user's endpoints, phishing clicks in the preceding days, or adversary-in-the-middle patterns (a successful MFA push the user does not remember approving is a classic). Finally, promote the hunt: schedule the KQL as a weekly analytics rule, and consider pairing it with a companion hunt for anomalous first-time device logons — impossible travel catches the thief; device anomaly often catches how they got in.


EmoticonEmoticon