Detection rules › Kusto

Device Registration from Malicious IP

Status
available
Severity
high
Time window
1h
Group by
Location, NewDevice_osPlatform, NewDevice_osVersion, SuspiciousIP, actor_alternateId_s, actor_displayName_s, client_ipAddress_s, client_userAgent_browser_s, client_userAgent_os_s, client_userAgent_rawUserAgent_s, displayMessage_s, eventType_s, outcome_reason_s, outcome_result_s, severity_s
Source
github.com/Azure/Azure-Sentinel

This rule identifies successful Okta device registration events that originate from IP addresses also observed by Okta ThreatInsight as malicious or suspicious.

MITRE ATT&CK coverage

Telemetry coverage

Rules detecting the same action

These rules filter on the same operation.

Rule body

id: e36c6bd6-f86a-4282-93a5-b4a1b48dd849
name: Device Registration from Malicious IP
description: |
  This rule identifies successful Okta device registration events that originate from IP addresses also observed by Okta ThreatInsight as malicious or suspicious.
severity: High
status: Available
requiredDataConnectors:
  - connectorId: OktaSSO
    dataTypes:
      - Okta_CL
  - connectorId: OktaSSOv2
    dataTypes:
      - OktaSSO
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Persistence
relevantTechniques:
  - T1098.005
query: |
  let Events = dynamic(["device.enrollment.create"]);
  let ThreatInsightOperations = dynamic(["security.threat.detected", "security.attack.start", "security.attack.end" ]);
  // Adjust list of allowed IPs to reduce false positives
  let AllowedIPs = dynamic([]);
  let DeviceRegistrations =  OktaSSO
  | where eventType_s in (Events)
  | where outcome_result_s == "SUCCESS"
  | where isnotempty(client_ipAddress_s) and client_ipAddress_s !in (AllowedIPs)
  | extend oktaDeviceId_ = tostring(parse_json(tostring(parse_json(target_s)[0].detailEntry)).oktaDeviceId), NewDevice_osPlatform = tostring(parse_json(tostring(parse_json(target_s)[0].detailEntry)).osPlatform),  NewDevice_osVersion = tostring(parse_json(tostring(parse_json(target_s)[0].detailEntry)).osVersion), displayName_ = tostring(parse_json(target_s)[0].displayName)
  | extend Location = strcat(client_geographicalContext_city_s,  " | ", client_geographicalContext_state_s," | ", client_geographicalContext_country_s)
  | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by actor_alternateId_s, actor_displayName_s, client_userAgent_os_s, client_ipAddress_s, displayMessage_s, outcome_result_s,
  outcome_reason_s, column_ifexists('debugContext_debugData_logOnlySecurityData_s', ""), column_ifexists('debugContext_debugData_threatSuspected_s',""), client_userAgent_rawUserAgent_s,client_userAgent_browser_s, severity_s, NewDevice_osPlatform, NewDevice_osVersion, eventType_s, Location ;
  let ThreatInsightEvents = OktaSSO
  | where eventType_s in (ThreatInsightOperations)
  | extend SuspiciousIP = actor_displayName_s
  | project TimeGenerated, column_ifexists('debugContext_debugData_threatDetections_s', ""), client_userAgent_rawUserAgent_s, severity_s, outcome_result_s, eventType_s, displayMessage_s, SuspiciousIP, transaction_id_s;
  DeviceRegistrations 
  | join kind=inner (ThreatInsightEvents) on $left.client_ipAddress_s == $right.SuspiciousIP
  | project TimeGenerated, actor_alternateId_s, actor_displayName_s, client_ipAddress_s, client_userAgent_os_s, client_userAgent_rawUserAgent_s, client_userAgent_browser_s, displayMessage_s, outcome_result_s, outcome_reason_s, severity_s, NewDevice_osPlatform, NewDevice_osVersion, eventType_s, Location, SuspiciousIP, transaction_id_s
  | 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
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: client_ipAddress_s
customDetails:
  ActorDisplayName: actor_displayName_s
  ActorDeviceOS: NewDevice_osPlatform
  ActorDeviceOSVersion: NewDevice_osVersion
  UserAgent: client_userAgent_rawUserAgent_s
  Location: Location
alertDetailsOverride:
  alertDisplayNameFormat: 'Device registration from Malicious IP {{client_ipAddress_s}}'
  alertDescriptionFormat: 'User {{actor_displayName_s}} ({{actor_alternateId_s}}) registered a device from {{client_ipAddress_s}} and the IP is associated with malicious activity in Okta ThreatInsight.'
version: 1.1.2
kind: Scheduled

Stages and Predicates

Parameters

let Events = dynamic(["device.enrollment.create"]);
let ThreatInsightOperations = dynamic(["security.threat.detected", "security.attack.start", "security.attack.end" ]);
let AllowedIPs = dynamic([]);

let DeviceRegistrations is inlined into the numbered stages below.

Let binding: ThreatInsightEvents used in Stage 8

let ThreatInsightEvents = OktaSSO
| where eventType_s in (ThreatInsightOperations)
| extend SuspiciousIP = actor_displayName_s
| project TimeGenerated, column_ifexists('debugContext_debugData_threatDetections_s', ""), client_userAgent_rawUserAgent_s, severity_s, outcome_result_s, eventType_s, displayMessage_s, SuspiciousIP, transaction_id_s;

Stages 1 to 7 define let DeviceRegistrations (the rule's main pipeline source); stages 8 to 10 run on it.

Stage 1: source

OktaSSO

Stage 2: where

| where eventType_s in (Events)

Stage 3: where

| where outcome_result_s == "SUCCESS"

Stage 4: where

| where isnotempty(client_ipAddress_s) and client_ipAddress_s !in (AllowedIPs)

Stage 5: extend

| extend oktaDeviceId_ = tostring(parse_json(tostring(parse_json(target_s)[0].detailEntry)).oktaDeviceId), NewDevice_osPlatform = tostring(parse_json(tostring(parse_json(target_s)[0].detailEntry)).osPlatform),  NewDevice_osVersion = tostring(parse_json(tostring(parse_json(target_s)[0].detailEntry)).osVersion), displayName_ = tostring(parse_json(target_s)[0].displayName)

Stage 6: extend

| extend Location = strcat(client_geographicalContext_city_s,  " | ", client_geographicalContext_state_s," | ", client_geographicalContext_country_s)

Stage 7: summarize

| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by actor_alternateId_s, actor_displayName_s, client_userAgent_os_s, client_ipAddress_s, displayMessage_s, outcome_result_s,
outcome_reason_s, column_ifexists('debugContext_debugData_logOnlySecurityData_s', ""), column_ifexists('debugContext_debugData_threatSuspected_s',""), client_userAgent_rawUserAgent_s,client_userAgent_browser_s, severity_s, NewDevice_osPlatform, NewDevice_osVersion, eventType_s, Location

Stage 8: join

DeviceRegistrations
| join kind=inner (ThreatInsightEvents) on $left.client_ipAddress_s == $right.SuspiciousIP

Stage 9: project

| project TimeGenerated, actor_alternateId_s, actor_displayName_s, client_ipAddress_s, client_userAgent_os_s, client_userAgent_rawUserAgent_s, client_userAgent_browser_s, displayMessage_s, outcome_result_s, outcome_reason_s, severity_s, NewDevice_osPlatform, NewDevice_osVersion, eventType_s, Location, SuspiciousIP, transaction_id_s

Stage 10: extend

| extend AccountName = tostring(split(actor_alternateId_s, "@")[0]), AccountUPNSuffix = tostring(split(actor_alternateId_s, "@")[1])

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
client_ipAddress_seq[]excludes:client_ipAddress_s field:"client_ipAddress_s" value:"[]"

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
client_ipAddress_sis_not_null
  • (no value, null check)
field:"okta::client.ipAddress" kind:is_not_null
eventType_sin
  • Events
  • security.attack.end
  • security.attack.start
  • security.threat.detected
field:"okta::eventType" kind:in
outcome_result_seq
  • SUCCESS
field:"okta::outcome.result" kind:eq value:"SUCCESS"

Output fields

These fields are emitted when the rule matches.

FieldSource
Locationproject
NewDevice_osPlatformproject
NewDevice_osVersionproject
SuspiciousIPproject
TimeGeneratedproject
actor_alternateId_sproject
actor_displayName_sproject
client_ipAddress_sproject
client_userAgent_browser_sproject
client_userAgent_os_sproject
client_userAgent_rawUserAgent_sproject
displayMessage_sproject
eventType_sproject
outcome_reason_sproject
outcome_result_sproject
severity_sproject
transaction_id_sproject
AccountNameextend
AccountUPNSuffixextend