ID | Technique | Tactic |
---|---|---|
T1078 | Valid Accounts | Defense Evasion |
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.
Search
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 |
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