Detection rules › Kusto
User Login from Different Countries within 3 hours
This rule detects successful user logins to the Okta Console from different countries within 3 hours. This may indicate a compromised account or an account being used by multiple individuals in different locations.
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Telemetry coverage
| Platform | Record / event type |
|---|---|
| Okta | System Log event type user.session.start: User logged in to Okta |
Rules detecting the same action
These rules filter on the same operation.
- Failed Logins from Unknown or Invalid User (Kusto)
- First Occurrence of Okta User Session Started via Proxy (Elastic)
- High-Risk Admin Activity (Kusto)
- Multiple Okta Sessions Detected for a Single User (Elastic)
- Multiple Okta User Authentication Events with Same Device Token Hash (Elastic)
- New Device/Location sign-in along with critical operation (Kusto)
- Okta AiTM Session Cookie Replay (Elastic)
- Okta Login From CrowdStrike Unmanaged Device (Panther)
Rule body
id: 2954d424-f786-4677-9ffc-c24c44c6e7d5
name: User Login from Different Countries within 3 hours
description: |
This rule detects successful user logins to the Okta Console from different countries within 3 hours. This may indicate a compromised account or an account being used by multiple individuals in different locations.
severity: High
status: Available
requiredDataConnectors:
- connectorId: OktaSSO
dataTypes:
- Okta_CL
- connectorId: OktaSSOv2
dataTypes:
- OktaSSO
queryFrequency: 3h
queryPeriod: 3h
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
relevantTechniques:
- T1078.004
query: |
let timeframe = ago(3h);
let threshold = 2;
OktaSSO
| where column_ifexists('published_t', now()) >= timeframe
| where eventType_s =~ "user.session.start"
| where outcome_result_s =~ "SUCCESS"
| where isnotempty(column_ifexists('client_geographicalContext_country_s', ''))
| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), NumOfCountries = dcount(tostring(column_ifexists('client_geographicalContext_country_s', ''))) by actor_alternateId_s
| where NumOfCountries >= threshold
| extend AccountName = tostring(split(actor_alternateId_s, "@")[0]), AccountUPNSuffix = tostring(split(actor_alternateId_s, "@")[1])
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: AccountName
- identifier: UPNSuffix
columnName: AccountUPNSuffix
customDetails:
StartTime: StartTime
EndTime: EndTime
NumOfCountries: NumOfCountries
alertDetailsOverride:
alertDisplayNameFormat: 'User {{actor_alternateId_s}} logged in from {{NumOfCountries}} countries within 3 hours'
alertDescriptionFormat: 'User {{actor_alternateId_s}} authenticated successfully from {{NumOfCountries}} countries within 3 hours.'
version: 1.1.2
kind: Scheduled
Stages and Predicates
Parameters
let timeframe = ago(3h);
let threshold = 2;
Stage 1: source
OktaSSO
Stage 2: where
| where column_ifexists('published_t', now()) >= timeframe
Stage 3: where
| where eventType_s =~ "user.session.start"
Stage 4: where
| where outcome_result_s =~ "SUCCESS"
Stage 5: where
| where isnotempty(column_ifexists('client_geographicalContext_country_s', ''))
Stage 6: summarize
| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), NumOfCountries = dcount(tostring(column_ifexists('client_geographicalContext_country_s', ''))) by actor_alternateId_s
Stage 7: where
| where NumOfCountries >= threshold
Stage 8: extend
| extend AccountName = tostring(split(actor_alternateId_s, "@")[0]), AccountUPNSuffix = tostring(split(actor_alternateId_s, "@")[1])
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
NumOfCountries | ge |
| field:"NumOfCountries" kind:ge value:"2" |
eventType_s | eq |
| field:"okta::eventType" kind:eq value:"user.session.start" |
outcome_result_s | eq |
| field:"okta::outcome.result" kind:eq value:"SUCCESS" |
published_t | ge |
| field:"published_t" kind:ge value:"timeframe" |
Output fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
EndTime | summarize |
NumOfCountries | summarize |
StartTime | summarize |
actor_alternateId_s | summarize |
AccountName | extend |
AccountUPNSuffix | extend |