Detection rules › Kusto

Insider Risk_High User Security Alert Correlations

Severity
medium
Time window
7d
Group by
AlertId, IncidentNumber, SystemAlertId, UPN
Source
github.com/Azure/Azure-Sentinel

'This alert joins SecurityAlerts from Microsoft Products with SecurityIncidents from Microsoft Sentinel and Microsoft Defender XDR. This join allows for identifying patterns in user principal names associated with respective security alerts. A machine learning function (Basket) is leveraged with a .001 threshold. Baset finds all frequent patterns of discrete attributes (dimensions) in the data. It returns the frequent patterns passed the frequency threshold. This query evaluates UserPrincipalName for patterns in SecurityAlerts and Reporting Security Tools. This query can be further tuned/configured for higher confidence percentages, security products, or alert severities pending the needs of the organization. There is an option for configuration of correlations against Microsoft Sentinel watchlists. For more information on the basket plugin, see basket plugin'

MITRE ATT&CK coverage

TacticTechniques
Execution

Rule body

id: a4fb4255-f55b-4c24-b396-976ee075d406
name: Insider Risk_High User Security Alert Correlations
description: |
  'This alert joins SecurityAlerts from Microsoft Products with SecurityIncidents from Microsoft Sentinel and Microsoft Defender XDR. This join allows for identifying patterns in user principal names associated with respective security alerts. A machine learning function (Basket) is leveraged with a .001 threshold. Baset finds all frequent patterns of discrete attributes (dimensions) in the data. It returns the frequent patterns passed the frequency threshold. This query evaluates UserPrincipalName for patterns in SecurityAlerts and Reporting Security Tools. This query can be further tuned/configured for higher confidence percentages, security products, or alert severities pending the needs of the organization. There is an option for configuration of correlations against Microsoft Sentinel watchlists. For more information on the basket plugin, see [basket plugin](https://docs.microsoft.com/azure/data-explorer/kusto/query/basketplugin)'
severity: Medium
requiredDataConnectors:
  - connectorId: MicrosoftDefenderAdvancedThreatProtection
    dataTypes:
      - SecurityAlert (MDATP)
  - connectorId: AzureActiveDirectoryIdentityProtection
    dataTypes:
      - SecurityAlert (IPC)
  - connectorId: AzureSecurityCenter
    dataTypes:
      - SecurityAlert (ASC)
  - connectorId: IoT
    dataTypes:
      - SecurityAlert (ASC for IoT)
  - connectorId: MicrosoftCloudAppSecurity
    dataTypes:
      - SecurityAlert (ASC for IoT)
  - connectorId: IoT
    dataTypes:
      - SecurityAlert (MCAS)
  - connectorId: OfficeATP
    dataTypes:
      - SecurityAlert (Office 365)
queryFrequency: 7d
queryPeriod: 7d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Execution
relevantTechniques:
  - T1204
query: |
  let AlertLinks = SecurityAlert
  | summarize hint.strategy = shuffle arg_max(TimeGenerated, *), NumberOfUpdates = count() by SystemAlertId
  | mv-expand todynamic(Entities)
  | where Entities["Type"] =~ "account"
  | extend Name = tostring(tolower(Entities["Name"])), NTDomain = tostring(Entities["NTDomain"]), UPNSuffix = tostring(Entities["UPNSuffix"]), AadUserId = tostring(Entities["AadUserId"]), AadTenantId = tostring(Entities["AadTenantId"]), 
            Sid = tostring(Entities["Sid"]), IsDomainJoined = tobool(Entities["IsDomainJoined"]), Host = tostring(Entities["Host"])
  | extend UPN = iff(Name != "" and UPNSuffix != "", strcat(Name, "@", UPNSuffix), "")
  | extend Href_ = tostring(parse_json(ExtendedLinks)[0].Href)
  | where UPN <> ""
  | summarize AlertLinks=make_set(AlertLink) by UPN;
  let LastTimeObserved = SecurityIncident
  | summarize hint.strategy = shuffle arg_max(LastModifiedTime, *) by IncidentNumber
  | mv-expand AlertIds
  | extend AlertId = tostring(AlertIds)
  | join kind= innerunique ( 
            SecurityAlert 
            )
            on $left.AlertId == $right.SystemAlertId
  | summarize hint.strategy = shuffle arg_max(TimeGenerated, *), NumberOfUpdates = count() by SystemAlertId
  | mv-expand todynamic(Entities)
  | where Entities["Type"] =~ "account"
  | extend Name = tostring(tolower(Entities["Name"])), NTDomain = tostring(Entities["NTDomain"]), UPNSuffix = tostring(Entities["UPNSuffix"]), AadUserId = tostring(Entities["AadUserId"]), AadTenantId = tostring(Entities["AadTenantId"]), 
            Sid = tostring(Entities["Sid"]), IsDomainJoined = tobool(Entities["IsDomainJoined"]), Host = tostring(Entities["Host"])
  | extend UPN = iff(Name != "" and UPNSuffix != "", strcat(Name, "@", UPNSuffix), "")
  | extend Href_ = tostring(parse_json(ExtendedLinks)[0].Href)
  | where UPN <> ""
  | project UPN, AlertName, Severity, ProductName, TimeGenerated | summarize arg_max(AlertName, Severity, ProductName, TimeGenerated) by UPN;
  SecurityIncident
  | summarize hint.strategy = shuffle arg_max(LastModifiedTime, *) by IncidentNumber
  | mv-expand AlertIds
  | extend AlertId = tostring(AlertIds)
  | join kind= innerunique ( 
            SecurityAlert 
            )
            on $left.AlertId == $right.SystemAlertId
  | summarize hint.strategy = shuffle arg_max(TimeGenerated, *), NumberOfUpdates = count() by SystemAlertId
  | mv-expand todynamic(Entities)
  | where Entities["Type"] =~ "account"
  | extend Name = tostring(tolower(Entities["Name"])), NTDomain = tostring(Entities["NTDomain"]), UPNSuffix = tostring(Entities["UPNSuffix"]), AadUserId = tostring(Entities["AadUserId"]), AadTenantId = tostring(Entities["AadTenantId"]), 
            Sid = tostring(Entities["Sid"]), IsDomainJoined = tobool(Entities["IsDomainJoined"]), Host = tostring(Entities["Host"])
  | extend UPN = iff(Name != "" and UPNSuffix != "", strcat(Name, "@", UPNSuffix), "")
  | extend Href_ = tostring(parse_json(ExtendedLinks)[0].Href)
  | where UPN <> ""
  | project UPN, AlertName, Severity, ProductName
  | evaluate basket(0.001)
  | where UPN <> ""
  // | lookup kind=inner _GetWatchlist('<Your Watchlist Name>') on $left.UPN == $right.SearchKey
  | project UPN, Percent, AlertName, Severity, ProductName
  | join (LastTimeObserved) on UPN
  | sort by Percent desc
  | extend LastObserved = TimeGenerated
  | join kind=inner (AlertLinks) on UPN
  | project UPN, AlertName, Severity, ProductName, Percent, LastObserved, AlertLinks
  | extend AccountName = tostring(split(UPN, "@")[0]), AccountUPNSuffix = tostring(split(UPN, "@")[1])
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: UPN
      - identifier: Name
        columnName: AccountName
      - identifier: UPNSuffix
        columnName: AccountUPNSuffix
version: 1.1.3
kind: Scheduled

Stages and Predicates

Stage 0: let

let AlertLinks = SecurityAlert
| summarize hint.strategy = shuffle arg_max(TimeGenerated, *), NumberOfUpdates = count() by SystemAlertId
| mv-expand todynamic(Entities)
| where Entities["Type"] =~ "account"
| extend Name = tostring(tolower(Entities["Name"])), NTDomain = tostring(Entities["NTDomain"]), UPNSuffix = tostring(Entities["UPNSuffix"]), AadUserId = tostring(Entities["AadUserId"]), AadTenantId = tostring(Entities["AadTenantId"]), 
          Sid = tostring(Entities["Sid"]), IsDomainJoined = tobool(Entities["IsDomainJoined"]), Host = tostring(Entities["Host"])
| extend UPN = iff(Name != "" and UPNSuffix != "", strcat(Name, "@", UPNSuffix), "")
| extend Href_ = tostring(parse_json(ExtendedLinks)[0].Href)
| where UPN <> ""
| summarize AlertLinks=make_set(AlertLink) by UPN;
let LastTimeObserved = SecurityIncident
| summarize hint.strategy = shuffle arg_max(LastModifiedTime, *) by IncidentNumber
| mv-expand AlertIds
| extend AlertId = tostring(AlertIds)
| join kind= innerunique ( 
          SecurityAlert 
          )
          on $left.AlertId == $right.SystemAlertId
| summarize hint.strategy = shuffle arg_max(TimeGenerated, *), NumberOfUpdates = count() by SystemAlertId
| mv-expand todynamic(Entities)
| where Entities["Type"] =~ "account"
| extend Name = tostring(tolower(Entities["Name"])), NTDomain = tostring(Entities["NTDomain"]), UPNSuffix = tostring(Entities["UPNSuffix"]), AadUserId = tostring(Entities["AadUserId"]), AadTenantId = tostring(Entities["AadTenantId"]), 
          Sid = tostring(Entities["Sid"]), IsDomainJoined = tobool(Entities["IsDomainJoined"]), Host = tostring(Entities["Host"])
| extend UPN = iff(Name != "" and UPNSuffix != "", strcat(Name, "@", UPNSuffix), "")
| extend Href_ = tostring(parse_json(ExtendedLinks)[0].Href)
| where UPN <> ""
| project UPN, AlertName, Severity, ProductName, TimeGenerated | summarize arg_max(AlertName, Severity, ProductName, TimeGenerated) by UPN;

Stage 1: source

SecurityIncident

Stage 2: summarize

| summarize hint.strategy = shuffle arg_max(LastModifiedTime, *) by IncidentNumber

Stage 3: mv-expand

| mv-expand AlertIds

Stage 4: extend

| extend AlertId = tostring(AlertIds)

Stage 5: join

| join kind= innerunique ( 
          SecurityAlert 
          )
          on $left.AlertId == $right.SystemAlertId

Stage 6: summarize

summarize NumberOfUpdates by SystemAlertId

Stage 7: mv-expand

mv-expand

Stage 8: where

where Type =~ "account"

Stage 9: extend (3 consecutive steps)

extend AadTenantId, AadUserId, Host, Href_, IsDomainJoined, NTDomain, Name, Sid, UPN, UPNSuffix

Stage 10: where

where UPN !~ ""

Stage 11: project

project AlertName, ProductName, Severity, UPN

Stage 12: evaluate

evaluate

Stage 13: where

where UPN !~ ""

Stage 14: project

project AlertName, Percent, ProductName, Severity, UPN

Stage 15: join

join (LastTimeObserved) on UPN

Stage 16: sort

sort by Percent

Stage 17: extend

extend LastObserved

Stage 18: join

| join kind=inner (AlertLinks) on UPN

Stage 19: project

project AlertLinks, AlertName, LastObserved, Percent, ProductName, Severity, UPN

Stage 20: extend

extend AccountName, AccountUPNSuffix

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
Typeeq
  • account corpus 13 (kusto 13)
field:"Type" kind:eq value:"account"

Output fields

These fields are emitted when the rule matches.

FieldSource
AlertLinksproject
AlertNameproject
LastObservedproject
Percentproject
ProductNameproject
Severityproject
UPNproject
AccountNameextend
AccountUPNSuffixextend