Detection rules › Kusto

Potential Kerberoasting

Severity
medium
Time window
1d
Group by
ClientIPAddress, Computer, Status, TargetDomainName, TargetUserName, TicketEncryptionType, TicketOptions
Author
Microsoft Security Research
Source
github.com/Azure/Azure-Sentinel

A service principal name (SPN) is used to uniquely identify a service instance in a Windows environment. Each SPN is usually associated with a service account. Organizations may have used service accounts with weak passwords in their environment. An attacker can try requesting Kerberos ticket-granting service (TGS) service tickets for any SPN from a domain controller (DC) which contains a hash of the Service account. This can then be used for offline cracking. This hunting query looks for accounts that are generating excessive requests to different resources within the last hour compared with the previous 24 hours. Normal users would not make an unusually large number of request within a small time window. This is based on 4769 events which can be very noisy so environment based tweaking might be needed.

MITRE ATT&CK coverage

TacticTechniques
Credential Access

Telemetry coverage

Rule body

id: 1572e66b-20a7-4012-9ec4-77ec4b101bc8
name: Potential Kerberoasting
description: |
  'A service principal name (SPN) is used to uniquely identify a service instance in a Windows environment.
  Each SPN is usually associated with a service account. Organizations may have used service accounts with weak passwords in their environment.
  An attacker can try requesting Kerberos ticket-granting service (TGS) service tickets for any SPN from a domain controller (DC) which contains a hash of the Service account. This can then be used for offline cracking.
  This hunting query looks for accounts that are generating excessive requests to different resources within the last hour compared with the previous 24 hours.  Normal users would not make an unusually large number of request within a small time window. This is based on 4769 events which can be very noisy so environment based tweaking might be needed.'
severity: Medium
requiredDataConnectors:
  - connectorId: SecurityEvents
    dataTypes:
      - SecurityEvent
  - connectorId: WindowsSecurityEvents
    dataTypes:
      - SecurityEvent
  - connectorId: WindowsForwardedEvents
    dataTypes:
      - WindowsEvent
queryFrequency: 1h
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CredentialAccess
relevantTechniques:
  - T1558
query: |
  let starttime = 1d;
  let endtime = 1h;
  let prev23hThreshold = 4;
  let prev1hThreshold = 15;
  let Kerbevent = (union isfuzzy=true
  (SecurityEvent
  | where TimeGenerated >= ago(starttime)
  | where EventID == 4769
  | parse EventData with * 'TicketEncryptionType">' TicketEncryptionType "<" *
  | where TicketEncryptionType == '0x17'
  | parse EventData with * 'TicketOptions">' TicketOptions "<" *
  | where TicketOptions == '0x40810000'
  | parse EventData with * 'Status">' Status "<" *
  | where Status == '0x0'
  | parse EventData with * 'ServiceName">' ServiceName "<" *
  | where ServiceName !contains "$" and ServiceName !contains "krbtgt"
  | parse EventData with * 'TargetUserName">' TargetUserName "<" *
  | where TargetUserName !contains "$@" and TargetUserName !contains ServiceName
  | parse EventData with * 'IpAddress">::ffff:' ClientIPAddress "<" *
  ),
  (
  WindowsEvent
  | where TimeGenerated >= ago(starttime)
  | where EventID == 4769 and EventData has '0x17' and EventData has '0x40810000' and EventData has 'krbtgt'
  | extend TicketEncryptionType = tostring(EventData.TicketEncryptionType)
  | where TicketEncryptionType == '0x17'
  | extend TicketOptions = tostring(EventData.TicketOptions)
  | where TicketOptions == '0x40810000'
  | extend Status = tostring(EventData.Status)
  | where Status == '0x0'
  | extend ServiceName = tostring(EventData.ServiceName)
  | where ServiceName !contains "$" and ServiceName !contains "krbtgt"
  | extend TargetUserName = tostring(EventData.TargetUserName)
  | where TargetUserName !contains "$@" and TargetUserName !contains ServiceName
  | extend ClientIPAddress = tostring(EventData.IpAddress)
  ));
  let Kerbevent23h = Kerbevent
  | where TimeGenerated >= ago(starttime) and TimeGenerated < ago(endtime)
  | summarize ServiceNameCountPrev23h = dcount(ServiceName), ServiceNameSet23h = makeset(ServiceName)
  by Computer, TargetUserName,TargetDomainName, ClientIPAddress, TicketOptions, TicketEncryptionType, Status
  | where ServiceNameCountPrev23h < prev23hThreshold;
  let Kerbevent1h =
  Kerbevent
  | where TimeGenerated >= ago(endtime)
  | summarize min(TimeGenerated), max(TimeGenerated), ServiceNameCountPrev1h = dcount(ServiceName), ServiceNameSet1h = makeset(ServiceName)
  by Computer, TargetUserName, TargetDomainName, ClientIPAddress, TicketOptions, TicketEncryptionType, Status;
  Kerbevent1h
  | join kind=leftanti
  (
  Kerbevent23h
  ) on TargetUserName, TargetDomainName
  // Threshold value set above is based on testing, this value may need to be changed for your environment.
  | where ServiceNameCountPrev1h > prev1hThreshold
  | project StartTime = min_TimeGenerated, EndTime = max_TimeGenerated, TargetUserName, Computer, ClientIPAddress, TicketOptions,
  TicketEncryptionType, Status, ServiceNameCountPrev1h, ServiceNameSet1h, TargetDomainName
  | extend HostName = tostring(split(Computer, ".")[0]), DomainIndex = toint(indexof(Computer, '.'))
  | extend HostNameDomain = iff(DomainIndex != -1, substring(Computer, DomainIndex + 1), Computer)
  | extend TargetAccount = strcat(TargetDomainName,  "\\", TargetUserName)
  | project-away DomainIndex
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: TargetAccount
      - identifier: Name
        columnName: TargetUserName
      - identifier: NTDomain
        columnName: TargetDomainName
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: Computer
      - identifier: HostName
        columnName: HostName
      - identifier: DnsDomain
        columnName: HostNameDomain
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: ClientIPAddress
version: 1.1.7
kind: Scheduled
metadata:
    source:
        kind: Community
    author:
        name: Microsoft Security Research
    support:
        tier: Community
    categories:
        domains: [ "Security - Others", "Identity" ]

Stages and Predicates

Parameters

let starttime = 1d;
let endtime = 1h;
let prev23hThreshold = 4;
let prev1hThreshold = 15;

let Kerbevent and let Kerbevent1h are inlined into the numbered stages below.

Let binding: Kerbevent23h used in Stage 34

let Kerbevent23h = Kerbevent
| where TimeGenerated >= ago(starttime) and TimeGenerated < ago(endtime)
| summarize ServiceNameCountPrev23h = dcount(ServiceName), ServiceNameSet23h = makeset(ServiceName)
by Computer, TargetUserName,TargetDomainName, ClientIPAddress, TicketOptions, TicketEncryptionType, Status
| where ServiceNameCountPrev23h < prev23hThreshold;

Stage 1: source

let Kerbevent

Stage 2: source

let Kerbevent1h

Stage 3: union

union of 2 branches

Stage 4: source

SecurityEvent

Stage 5: where

where TimeGenerated >= ago(86400s)

Stage 6: where

where EventID == 4769

Stage 7: parse

parse

Stage 8: where

where TicketEncryptionType == 0x17

Stage 9: parse

parse

Stage 10: where

where TicketOptions == 0x40810000

Stage 11: parse

parse

Stage 12: where

where Status == 0x0

Stage 13: parse

parse

Stage 14: where

where not (ServiceName contains "$") and not (ServiceName contains "krbtgt")

Stage 15: parse

parse

Stage 16: where

where not (TargetUserName contains "$@") and TargetUserName !contains ServiceName

Stage 17: parse

parse

Stage 18: source

WindowsEvent

Stage 19: where

where TimeGenerated >= ago(86400s)

Stage 20: where

where EventData contains 0x17 and EventData contains 0x40810000 and EventData contains "krbtgt" and EventID == 4769

Stage 21: extend

extend TicketEncryptionType

Stage 22: where

where TicketEncryptionType == 0x17

Stage 23: extend

extend TicketOptions

Stage 24: where

where TicketOptions == 0x40810000

Stage 25: extend

extend Status

Stage 26: where

where Status == 0x0

Stage 27: extend

extend ServiceName

Stage 28: where

where not (ServiceName contains "$") and not (ServiceName contains "krbtgt")

Stage 29: extend

extend TargetUserName

Stage 30: where

where not (TargetUserName contains "$@") and TargetUserName !contains ServiceName

Stage 31: extend

extend ClientIPAddress

Stage 32: where

where TimeGenerated >= ago(3600s)

Stage 33: summarize

summarize ServiceNameCountPrev1h, ServiceNameSet1h by Computer, TargetUserName, TargetDomainName, ClientIPAddress, TicketOptions, TicketEncryptionType, Status

Stage 34: join (negated)

join kind=leftanti (Kerbevent23h) on TargetUserName, TargetDomainName

Stage 35: where

where ServiceNameCountPrev1h > 15

Stage 36: project

project ClientIPAddress, Computer, EndTime, ServiceNameCountPrev1h, ServiceNameSet1h, StartTime, Status, TargetDomainName, TargetUserName, TicketEncryptionType, TicketOptions

Stage 37: extend (3 consecutive steps)

extend DomainIndex, HostName, HostNameDomain, TargetAccount

Stage 38: project-away

project-away DomainIndex

Exclusions

The rule actively suppresses these predicates.

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
EventDatamatch
  • 0x17 transforms: term
  • 0x40810000 transforms: term
  • krbtgt transforms: term
field:"EventData" kind:match
EventIDeq
  • 4769 corpus 11 (splunk 6, kusto 4, elastic 1)
field:"EventID" kind:eq value:"4769"
ServiceNamecontains
  • $
  • krbtgt
field:"ServiceName" kind:contains
ServiceNameCountPrev1hgt
  • 15
field:"ServiceNameCountPrev1h" kind:gt value:"15"
Statuseq
  • 0x0 corpus 5 (elastic 2, kusto 2, sigma 1)
field:"Status" kind:eq value:"0x0"
TargetUserNamecontains
  • $@
field:"TargetUserName" kind:contains value:"$@"
TargetUserNamecross_field_compare
  • ServiceName transforms: op:not_contains corpus 2 (kusto 2)
field:"TargetUserName" kind:cross_field_compare value:"ServiceName"
TicketEncryptionTypeeq
  • 0x17 corpus 9 (splunk 4, sigma 3, elastic 1, kusto 1)
field:"TicketEncryptionType" kind:eq value:"0x17"
TicketOptionseq
  • 0x40810000 corpus 4 (splunk 2, sigma 1, kusto 1)
field:"TicketOptions" kind:eq value:"0x40810000"

Output fields

These fields are emitted when the rule matches.

FieldSource
ClientIPAddressproject
Computerproject
EndTimeproject
ServiceNameCountPrev1hproject
ServiceNameSet1hproject
StartTimeproject
Statusproject
TargetDomainNameproject
TargetUserNameproject
TicketEncryptionTypeproject
TicketOptionsproject
HostNameextend
HostNameDomainextend
TargetAccountextend