Detection rules › Kusto

Detect Potential Kerberoast Activities

Status
available
Severity
medium
Time window
2m
Group by
AccountSid, AccountUpn, DeviceName
Source
github.com/Azure/Azure-Sentinel

This query aim to detect if someone requests service tickets (where count => maxcount) The query requires trimming to set a baseline level for MaxCount
Mitre Technique: Kerberoasting (T1558.003) @MattiasBorg82

MITRE ATT&CK coverage

Telemetry coverage

Rule body

id: 12134de5-361b-427c-a1a0-d43f40a593c4
name: Detect Potential Kerberoast Activities
description: |
  This query aim to detect if someone requests service tickets (where count => maxcount)
  The query requires trimming to set a baseline level for MaxCount  
  Mitre Technique: Kerberoasting (T1558.003)
  @MattiasBorg82
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: MicrosoftThreatProtection
    dataTypes:
      - IdentityLogonEvents
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CredentialAccess
relevantTechniques:
  - T1558.003
tag:
  - Kerberoast
query: |
  let MaxCount = 70; //Number of requests per 2 minute timeframe, depending on org size.
  IdentityLogonEvents
  | where ActionType == "LogonSuccess"
  | where Protocol == "Kerberos"
  | extend json = todynamic(parse_json(tostring(AdditionalFields)))
  | extend SPN = json.Spns,
         AttackTechniques = json.AttackTechniques
        | project-away json
  | where isnotempty(SPN)
  | where AttackTechniques has "T1558.003"
  | mv-expand SPN
          | extend SPNType = tostring(extract(@"^\w+",0,tostring(SPN)))
  | distinct tostring(SPN), DeviceName, AccountUpn, AccountSid, bin(TimeGenerated, 2m), ReportId, tostring(AttackTechniques)
  | summarize count(), SPNS=(make_list(SPN, 100000)),ReportId=tostring((make_list(ReportId, 100000))[0]) by AccountUpn,AccountSid,DeviceName, bin(TimeGenerated, 2m), tostring(AttackTechniques)
  | extend SPNS = (replace_regex(tostring(SPNS), @'[^\w+-\/]+', '')) 
  | where count_ >= MaxCount
  | extend HostName = iff(DeviceName has '.', substring(DeviceName, 0, indexof(DeviceName, '.')), DeviceName)
  | extend DnsDomain = iff(DeviceName has '.', substring(DeviceName, indexof(DeviceName, '.') + 1), "")
  | extend AccountDomain = tostring(split(AccountUpn, '@')[1]), AccountName = tostring(split(AccountUpn, '@')[0])
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: DeviceName
      - identifier: HostName
        columnName: HostName
      - identifier: DnsDomain
        columnName: DnsDomain
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: AccountUpn
      - identifier: DnsDomain
        columnName: AccountDomain
      - identifier: Name
        columnName: AccountName
version: 1.0.0
kind: Scheduled

Stages and Predicates

Parameters

let MaxCount = 70;

Stage 1: source

IdentityLogonEvents

Stage 2: where

| where ActionType == "LogonSuccess"

Stage 3: where

| where Protocol == "Kerberos"

Stage 4: extend

| extend json = todynamic(parse_json(tostring(AdditionalFields)))

Stage 5: extend

| extend SPN = json.Spns,
       AttackTechniques = json.AttackTechniques

Stage 6: project-away

| project-away json

Stage 7: where

| where isnotempty(SPN)

Stage 8: where

| where AttackTechniques has "T1558.003"

Stage 9: mv-expand

| mv-expand SPN

Stage 10: extend

| extend SPNType = tostring(extract(@"^\w+",0,tostring(SPN)))

Stage 11: distinct

| distinct tostring(SPN), DeviceName, AccountUpn, AccountSid, bin(TimeGenerated, 2m), ReportId, tostring(AttackTechniques)

Stage 12: summarize

| summarize count(), SPNS=(make_list(SPN, 100000)),ReportId=tostring((make_list(ReportId, 100000))[0]) by AccountUpn,AccountSid,DeviceName, bin(TimeGenerated, 2m), tostring(AttackTechniques)

Stage 13: extend

| extend SPNS = (replace_regex(tostring(SPNS), @'[^\w+-\/]+', ''))

Stage 14: where

| where count_ >= MaxCount

Stage 15: extend (3 consecutive steps)

| extend HostName = iff(DeviceName has '.', substring(DeviceName, 0, indexof(DeviceName, '.')), DeviceName)
| extend DnsDomain = iff(DeviceName has '.', substring(DeviceName, indexof(DeviceName, '.') + 1), "")
| extend AccountDomain = tostring(split(AccountUpn, '@')[1]), AccountName = tostring(split(AccountUpn, '@')[0])
HostName =
ifDeviceName has "."substring(DeviceName, 0, indexof(DeviceName, '.'))
elseDeviceName

Indicators

These rows show field, operator, and value matches.

Output fields

These fields are emitted when the rule matches.

FieldSource
AccountSidsummarize
AccountUpnsummarize
DeviceNamesummarize
ReportIdsummarize
SPNSextend
HostNameextend
DnsDomainextend
AccountDomainextend
AccountNameextend