MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Rule body
id: e7dbcbc3-b18f-4635-b27c-718195c369f1
name: Claroty - Login to uncommon location
description: 'Detects user login to uncommon location.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: CefAma
dataTypes:
- CommonSecurityLog
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
relevantTechniques:
- T1190
- T1133
query: |
let usr_sites = ClarotyEvent
| where TimeGenerated > ago(14d)
| where EventType has 'Login to SRA succeeded'
| extend Site = column_ifexists("site_name","")
| where isnotempty(Site)
| extend SrcUsername = extract(@'User\s(.*?)\slogged', 1, EventMessage)
| where isnotempty(SrcUsername)
| summarize all_loc = make_set(tostring(Site)) by SrcUsername;
ClarotyEvent
| where TimeGenerated > ago(14d)
| where EventType has 'Login to SRA succeeded'
| extend Site = column_ifexists("site_name","")
| where isnotempty(Site)
| extend SrcUsername = extract(@'User\s(.*?)\slogged', 1, EventMessage)
| where isnotempty(SrcUsername)
| join kind=leftouter (usr_sites) on SrcUsername
| where isempty(all_loc) or not(set_has_element(all_loc, tostring(Site)))
| extend SrcIpAddr
| summarize StartTime=min(TimeGenerated), EndTime=max(TimeGenerated), EventCount=count(), Sites=make_set(Site, 100), KnownSites=any(all_loc) by SrcUsername, SrcIpAddr, Site, EventType, EventMessage
| extend UncommonSiteCount = array_length(Sites)
| project StartTime, EndTime, SrcUsername, SrcIpAddr, Site, EventType, EventMessage, EventCount, UncommonSiteCount, KnownSites
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SrcIpAddr
version: 1.0.4
kind: Scheduled
Stages and Predicates
Let binding: usr_sites
let usr_sites = ClarotyEvent
| where TimeGenerated > ago(14d)
| where EventType has 'Login to SRA succeeded'
| extend Site = column_ifexists("site_name","")
| where isnotempty(Site)
| extend SrcUsername = extract(@'User\s(.*?)\slogged', 1, EventMessage)
| where isnotempty(SrcUsername)
| summarize all_loc = make_set(tostring(Site)) by SrcUsername;
Stage 1: source
ClarotyEvent
Stage 2: where
| where TimeGenerated > ago(14d)
Stage 3: where
| where EventType has 'Login to SRA succeeded'
Stage 4: extend
| extend Site = column_ifexists("site_name","")
Stage 5: where
| where isnotempty(Site)
Stage 6: extend
| extend SrcUsername = extract(@'User\s(.*?)\slogged', 1, EventMessage)
Stage 7: where
| where isnotempty(SrcUsername)
Stage 8: join
| join kind=leftouter (usr_sites) on SrcUsername
Stage 9: where
| where isempty(all_loc) or not(set_has_element(all_loc, tostring(Site)))
Stage 10: extend
| extend SrcIpAddr
Stage 11: summarize
| summarize StartTime=min(TimeGenerated), EndTime=max(TimeGenerated), EventCount=count(), Sites=make_set(Site, 100), KnownSites=any(all_loc) by SrcUsername, SrcIpAddr, Site, EventType, EventMessage
Stage 12: extend
| extend UncommonSiteCount = array_length(Sites)
Stage 13: project
| project StartTime, EndTime, SrcUsername, SrcIpAddr, Site, EventType, EventMessage, EventCount, UncommonSiteCount, KnownSites
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
EventType | match |
| field:"EventType" kind:match value:"Login to SRA succeeded" |
Site | is_not_null | field:"Site" kind:is_not_null | |
SrcUsername | is_not_null | field:"SrcUsername" kind:is_not_null | |
all_loc | is_null | field:"all_loc" kind:is_null |
Output fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
EndTime | project |
EventCount | project |
EventMessage | project |
EventType | project |
KnownSites | project |
Site | project |
SrcIpAddr | project |
SrcUsername | project |
StartTime | project |
UncommonSiteCount | project |