Detection rules › Kusto

User Login from Different Countries within 3 hours

Status
available
Severity
high
Time window
3h
Group by
actor_alternateId_s
Source
github.com/Azure/Azure-Sentinel

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

TacticTechniques
Initial Access

Telemetry coverage

Rules detecting the same action

These rules filter on the same operation.

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.

Output fields

These fields are emitted when the rule matches.

FieldSource
EndTimesummarize
NumOfCountriessummarize
StartTimesummarize
actor_alternateId_ssummarize
AccountNameextend
AccountUPNSuffixextend