Concurrent Multi-Geography Sessions on One Identity

Identity / cloud

Behavioral Hunt medium DPRK IT Worker FraudLaptop Farm

Impossible-travel and overlapping-session logic aimed at the specific case where a laptop physically sits in one country while the person operating it is in another.

What this hunt looks for

DPRK IT-worker fraud relies on facilitators hosting laptop farms, residences or offices racked with employer-issued laptops, remotely controlled from overseas through commercial remote-access tools or hardware KVMs and frequently egressing through Astrill VPN. Christina Chapman's 2025 sentencing, over a farm of more than ninety devices used to defraud 309 companies, is the documented example.

Whatever the transport, the geography does not reconcile: the facilitator's side checks in from where the farm is while the operator signs in from where they are, so one account appears in two countries at once. This hunt groups successful sign-ins by account and hour and reports any account seen in two or more countries.

Technique
Remote Access Tools abuse (MITRE ATT&CK T1219, T1219.002) combined with impossible-travel and valid-account misuse (T1078) consistent with laptop-farm operations.
Data sources
Identity provider sign-in logs
Last updated
2026-10-04

Logic

Every query below implements these steps, so the same hunt returns the same result in each product.

  1. Collect: successful interactive sign-ins from Entra ID (SigninLogs) or Okta, last 30 days
  2. Match: the same account succeeds from two or more countries inside one hour (country taken from the source IP)
  3. Exclude: corporate egress and sanctioned VPN addresses
  4. Report: account, hour, countries, addresses and sign-in count, newest first

Queries

Defender / Sentinel KQL

Written for this site

Each row is one account that succeeded from two or more countries inside one fixed hour; replace the placeholder egress addresses before running it.

// Replace the placeholder addresses with the organisation's corporate egress and sanctioned VPN ranges.
let CorporateEgress = dynamic(["203.0.113.10", "203.0.113.11"]);
SigninLogs
| where TimeGenerated > ago(30d)
| where ResultType == "0"
| where IPAddress !in (CorporateEgress)
| extend Country = tostring(LocationDetails.countryOrRegion)
| where isnotempty(Country)
| summarize Countries = make_set(Country), Addresses = make_set(IPAddress, 10), SignIns = count()
    by UserPrincipalName, Hour = bin(TimeGenerated, 1h)
| where array_length(Countries) >= 2
| sort by Hour desc

Splunk SPL

Written for this site

Each row is one account seen in two or more countries inside one fixed hour, from Entra ID or Okta sign-ins.

```sourcetype placeholders: ms:aad:signin (Entra ID, interactive) and okta; user, src_ip and action follow the CIM Authentication model```
```replace the placeholder addresses with the organisation's corporate egress and sanctioned VPN ranges```
index=identity (sourcetype="ms:aad:signin" OR sourcetype=okta) action=success earliest=-30d
| search NOT src_ip IN ("203.0.113.10", "203.0.113.11")
| iplocation src_ip
| where isnotnull(Country) AND Country!=""
| bin _time span=1h
| stats dc(Country) AS country_count values(Country) AS countries values(src_ip) AS addresses count AS sign_ins BY user _time
| where country_count>=2
| sort 0 - _time
| convert ctime(_time)

Elastic

Written for this site

Each row is one Entra ID account seen in two or more countries inside one fixed hour; replace the placeholder 203.0.113.0/24 with the corporate egress ranges.

FROM logs-azure.signinlogs-*
| WHERE @timestamp > NOW() - 30 days AND event.outcome == "success" AND source.geo.country_iso_code IS NOT NULL
| WHERE NOT CIDR_MATCH(source.ip, "203.0.113.0/24")
| EVAL hour = DATE_TRUNC(1 hour, @timestamp)
| STATS countries = COUNT_DISTINCT(source.geo.country_iso_code), country_list = VALUES(source.geo.country_iso_code), addresses = VALUES(source.ip), sign_ins = COUNT(*) BY azure.signinlogs.properties.user_principal_name, hour
| WHERE countries >= 2
| SORT hour DESC
| LIMIT 200

CrowdStrike LogScale

Written for this site

Each row is one account seen in two or more countries inside one fixed hour, with the country looked up from the source IP by LogScale.

// Entra ID sign-ins parsed into ECS-style fields (user.name, source.ip, event.outcome); replace the placeholder egress range.
event.outcome=success
| !cidr(source.ip, subnet="203.0.113.0/24")
| ipLocation(field=source.ip)
| source.ip.country=*
| bucket(span=1h, field=user.name, function=[count(as=sign_ins), count(source.ip.country, distinct=true, as=countries), collect([source.ip.country, source.ip])])
| countries >= 2
| sort(_bucket, order=desc)

False positives and tuning

Legitimate travel, corporate VPN egress and sanctioned remote-support sessions all trigger this, and most identity platforms already ship an impossible-travel detection tuned into silence long ago; where the tenant has Microsoft Entra ID Protection's atypical-travel alerts, read those alongside this result.

Hardware KVM usage never appears in endpoint process telemetry, so pair this hunt with the hardware hunts and with physical-asset and shipping-address review, not EDR alone.

Mandiant recommends verifying that a corporate laptop geolocates where the employee says they live, a control rather than a query that is worth implementing alongside this.

Fixed hourly bins miss a pair of sign-ins that straddles an hour boundary, and mobile carriers and consumer VPNs geolocate to the wrong country often enough that every hit needs a human look at the two addresses.

Sources

Tuning required before use This hunt is a starting point. Adapt the queries to your schema, validate them against your baseline and tune them for the false positives noted above. Indicators go stale as operators rotate hosts.