Detection: Geographic Improbable Location

EXPERIMENTAL DETECTION

This detection status is set to experimental. The Splunk Threat Research team has not yet fully tested, simulated, or built comprehensive datasets for this detection. As such, this analytic is not officially supported. If you have any questions or concerns, please reach out to us at research@splunk.com.

Description

Geolocation data can be inaccurate or easily spoofed by Remote Employment Fraud (REF) workers. REF actors sometimes slip up and reveal their true location, creating what we call 'improbable travel' scenarios — logins from opposite sides of the world within minutes. This identifies situations where these travel scenarios occur.

 1
 2| tstats summariesonly=true values(Authentication.app) as app from datamodel=Authentication.Authentication where (`okta` OR (index="firewall" AND sourcetype="pan:globalprotect")) AND Authentication.action="success" AND Authentication.app IN ("Workday", "Slack", "*GlobalProtect", "Jira*", "Atlassian Cloud", "Zoom") AND NOT Authentication.user="unknown" by _time index sourcetype host Authentication.user Authentication.src span=1s 
 3| `drop_dm_object_name("Authentication")` 
 4| fields user,src,app,_time,count,host 
 5| eval user=lower(replace(user, "((^.*\\\)
 6|(@.*$))", "")) 
 7| join type=outer user [
 8| inputlookup identity_lookup_expanded where user_status=active 
 9| rex field=email "^(?<user>[a-zA-Z0-9_\-\.]+)@([a-zA-Z0-9_\-\.]+)\.([a-zA-Z]{2,5})$" 
10| rename email as user_email bunit as user_bunit priority as user_priority work_country as user_work_country work_city as user_work_city 
11| fields user user_email user_bunit user_priority user_work_country user_work_city] 
12| eventstats dc(src) as src_count by user 
13| eventstats dc(user) as user_count by src 
14| sort 0 + _time 
15| iplocation src 
16| lookup local=true asn_lookup_by_cidr ip as src OUTPUT ip asn description 
17| eval session_lat=if(isnull(src_lat), lat, src_lat), session_lon=if(isnull(src_long), lon, src_long), session_city=if(isnull(src_city), City, src_city), session_country=if(isnull(src_country), Country, src_country), session_region=if(isnull(src_region), Region, src_region) 
18| eval session_city=if(isnull(session_city) OR match(session_city,"^\s+
19|^$"), null(), session_city), session_country=if(isnull(session_country) OR match(session_country,"^\s+
20|^$"), null(), session_country), session_region=if(isnull(session_region) OR match(session_region,"^\s+
21|^$"), null(), session_region) 
22| where isnotnull(session_lat) and isnotnull(session_lon) 
23| eval session_city=if(isnull(session_city),"-",session_city), session_country=if(isnull(session_country),"-",session_country), session_region=if(isnull(session_region),"-",session_region) 
24| streamstats current=t window=2 earliest(session_region) as prev_region,earliest(session_lat) as prev_lat, earliest(session_lon) as prev_lon, earliest(session_city) as prev_city, earliest(session_country) as prev_country, earliest(_time) as prev_time, earliest(src) as prev_src, latest(user_bunit) as user_bunit, earliest(app) as prev_app values(user_work_country) as user_work_country by user 
25| where (src!=prev_src) AND !(prev_city=session_city AND prev_country=session_country) AND ((isnotnull(prev_city) AND isnotnull(session_city)) OR prev_country!=session_country) 
26| `globedistance(session_lat,session_lon,prev_lat,prev_lon,"m")` 
27| eval time_diff=if((_time-prev_time)==0, 1, _time - prev_time) 
28| eval speed = round(distance*3600/time_diff,2) 
29| eval distance= round(distance,2) 
30| eval user_work_country=case(user_work_country="usa","United States", user_work_country="cze","Czechia", user_work_country="pol","Poland", user_work_country="ind","India", user_work_country="fra","France", user_work_country="can","Canada", user_work_country="mys","Malaysia", user_work_country="kor","South Korea", user_work_country="aus","Australia", user_work_country="bel","Belgium", user_work_country="dnk","Denmark", user_work_country="bra","Brazil", user_work_country="deu","Germany", user_work_country="jpn","Japan", user_work_country="che","Switzerland", user_work_country="swe","Sweden", user_work_country="zaf","South Africa", user_work_country="irl","Ireland", user_work_country="ita","Italy", user_work_country="nor","Norway", user_work_country="gbr","United Kingdom", user_work_country="hkg","Hong Kong", user_work_country="chn","China", user_work_country="esp","Spain", user_work_country="nld", "Netherlands", user_work_country="twn","Taiwan", user_work_country="est","Estonia", user_work_country="sgp","Singapore", user_work_country="are","United Arab Emirates", 1=1,"N/A") 
31| lookup local=true asn_lookup_by_cidr ip as prev_src OUTPUT ip as prev_ip asn as prev_asn description as prev_description 
32| eval suspect=if(!user_work_country==session_country,"Sketchy","Normal") 
33| search (speed>500 AND distance>750) 
34| table _time,prev_time,user,host,src,prev_src,app,prev_app,distance,speed,suspect,session_city,session_region, session_country,prev_city,prev_region,prev_country,user_priority,user_work_*,prev_ip,ip,asn,prev_asn,prev_description,description 
35| rename _time as event_time 
36| convert ctime(event_time) timeformat="%Y-%m-%d %H:%M:%S" 
37| convert ctime(prev_time) timeformat="%Y-%m-%d %H:%M:%S" 
38| eval problem=if(!session_country==prev_country AND (!session_country==user_work_country),"Yes","Nope") 
39| search NOT (prev_city="-" OR session_city="-") AND NOT [inputlookup known_devices_public_ip_filter.csv 
40| fields ip 
41| rename ip as src] 
42| dedup user host prev_src src 
43| fillnull value="N/A" 
44| search problem="Yes"
45| `geographic_improbable_location_filter`

Data Source

Name Platform Sourcetype Source
Okta N/A 'OktaIM2:log' 'Okta'

Macros Used

Name Value
okta eventtype=okta_log OR sourcetype = "OktaIM2:log"
geographic_improbable_location_filter search *
geographic_improbable_location_filter is an empty macro by default. It allows the user to filter out any results (false positives) without editing the SPL.

Annotations

Default Configuration

This detection is configured by default in Splunk Enterprise Security to run with the following settings:

Setting Value
Disabled true
Cron Schedule 0 * * * *
Earliest Time -70m@m
Latest Time -10m@m
Schedule Window auto
Creates Risk Event True
This configuration file applies to all detections of type anomaly. These detections will use Risk Based Alerting.

Implementation

The analytic leverages Okta OktaIm2 logs to be ingested using the Splunk Add-on for Okta Identity Cloud (https://splunkbase.splunk.com/app/6553). This also utilizes Splunk Enterprise Security Suite for several macros and lookups. The known_devices_public_ip_filter lookup is a placeholder for known public edge devices in your network.

Known False Positives

Legitimate usage of some VPNs may cause false positives. Tune as needed.

Associated Analytic Story

Risk Based Analytics (RBA)

Risk Message:

Improbable travel speed between locations observed for $user$.

Risk Object Risk Object Type Risk Score Threat Objects
user user 50 No Threat Objects

Detection Testing

Test Type Status Dataset Source Sourcetype
Validation Not Applicable N/A N/A N/A
Unit ❌ Failing N/A N/A N/A
Integration ❌ Failing N/A N/A N/A

Replay any dataset to Splunk Enterprise by using our replay.py tool or the UI. Alternatively you can replay a dataset into a Splunk Attack Range


Source: GitHub | Version: 1