Detection rules › Kusto
Potentially Relayed NTLM Authentication - Microsoft Sentinel
The below query detects Kerberos logons of computer accounts where there isn't any ticket request in the last 12h (10h is the default ticket expiration) coming from the same IpAddress with the same TargetUserName. The query can be enriched further if needed.
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Credential Access | No specific technique |
Telemetry coverage
Rule body
// Author : Cyb3rMonk(https://twitter.com/Cyb3rMonk, https://mergene.medium.com)
//
// Link to original post:
// https://posts.bluraven.io/detecting-kerberos-relaying-e6be66fa647c
//
// Description: This query detects Kerberos logons of computer accounts where there isn't any ticket request in the last 12h (10h is the default ticket expiration) coming from the same IpAddress with the same TargetUserName. The query can be enriched further if needed.
//
// Query parameters:
//
let Ticket_Requests = materialize (
SecurityEvent
| where TimeGenerated > ago(12h)
| where EventID == 4769
| where EventData has '<Data Name="Status">0x0</Data>'
| where EventData !has'<Data Name="IpAddress">::1</Data>'
| parse EventData with * 'TargetUserName">' TargetUserName '</Data' * 'TargetDomainName">' TargetDomainName '</Data' * 'ServiceName">' ServiceName '<' * 'IpAddress">::ffff:' IpAddress '<' * 'Status">' Status '<' *
| where TargetUserName !has ServiceName
| where TargetUserName contains "$"
| where ServiceName has "$"
| project TimeGenerated, TargetUserName=tolower(TargetUserName), TargetDomainName, ServiceName=tolower(replace_string(ServiceName, '$', '')), IpAddress, Status
)
;
let Suspicious_Logons =
Ticket_Requests
| join kind=rightanti (
SecurityEvent
| where TimeGenerated > ago(1h)
| where EventID == 4624
| where AuthenticationPackageName == "Kerberos"
| where IpAddress !in ('-', '::1', '127.0.0.1')
| where IpAddress !startswith "169.254."
| where Account endswith_cs "$"
| project TimeGenerated, Computer = tolower(replace_regex(Computer, @'(\w+)\..*', @'\1')), Account, TargetUserName=tolower(TargetUserName), IpAddress
| where TargetUserName !has Computer
) on IpAddress, $left.ServiceName==$right.Computer
;
Suspicious_Logons
| join kind=leftouter (
Ticket_Requests
| extend TargetUserName = replace_regex(TargetUserName, @'(\w+\$)@.*', @'\1')
) on IpAddress, TargetUserName
| summarize FirstSeen=min(TimeGenerated), LastSeen=max(TimeGenerated), count(), dcount(ServiceName) by TargetUserName, IpAddress
// Filter results
// we don't expect a successful ticket request coming from the rogue(attacker) device befor the relaying attack.
// If there is at least one ticket request coming from the suspicious IP with the same TargetUserName, assume it's a legitimate activity.
| where isempty(dcount_ServiceName)
Stages and Predicates
let Ticket_Requests and let Suspicious_Logons are inlined into the numbered stages below.
Stage 0: let
let Ticket_Requests = materialize(<inlined as stages below>);
let Suspicious_Logons = Ticket_Requests <inlined as stages below>;
Stages 1 to 13 define let Suspicious_Logons (the rule's main pipeline source); stages 14 to 16 run on it.
Stage 1: source
let Ticket_Requests
Stage 2: source
let Suspicious_Logons
Stage 3: source
SecurityEvent
Stage 4: where
| where TimeGenerated > ago(12h)
Stage 5: where
| where EventID == 4769
Stage 6: where
| where EventData has '<Data Name="Status">0x0</Data>'
Stage 7: where
| where EventData !has'<Data Name="IpAddress">::1</Data>'
Stage 8: parse
| parse EventData with * 'TargetUserName">' TargetUserName '</Data' * 'TargetDomainName">' TargetDomainName '</Data' * 'ServiceName">' ServiceName '<' * 'IpAddress">::ffff:' IpAddress '<' * 'Status">' Status '<' *
Stage 9: where
| where TargetUserName !has ServiceName
Stage 10: where
| where TargetUserName contains "$"
Stage 11: where
| where ServiceName has "$"
Stage 12: project
| project TimeGenerated, TargetUserName=tolower(TargetUserName), TargetDomainName, ServiceName=tolower(replace_string(ServiceName, '$', '')), IpAddress, Status
Stage 13: join
| join kind=rightanti (
SecurityEvent
| where TimeGenerated > ago(1h)
| where EventID == 4624
| where AuthenticationPackageName == "Kerberos"
| where IpAddress !in ('-', '::1', '127.0.0.1')
| where IpAddress !startswith "169.254."
| where Account endswith_cs "$"
| project TimeGenerated, Computer = tolower(replace_regex(Computer, @'(\w+)\..*', @'\1')), Account, TargetUserName=tolower(TargetUserName), IpAddress
| where TargetUserName !has Computer
) on IpAddress, $left.ServiceName==$right.Computer
Stage 14: join
Suspicious_Logons
| join kind=leftouter (
Ticket_Requests
| extend TargetUserName = replace_regex(TargetUserName, @'(\w+\$)@.*', @'\1')
) on IpAddress, TargetUserName
Stage 15: summarize
| summarize FirstSeen=min(TimeGenerated), LastSeen=max(TimeGenerated), count(), dcount(ServiceName) by TargetUserName, IpAddress
Stage 16: where
| where isempty(dcount_ServiceName)
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
EventData | match | <Data Name="IpAddress">::1</Data> | excludes:EventData |
IpAddress | in | -, 127.0.0.1, ::1 | excludes:IpAddress field:"IpAddress" value:"-" field:"IpAddress" value:"127.0.0.1" field:"IpAddress" value:"::1" |
IpAddress | starts_with | 169.254. | excludes:IpAddress field:"IpAddress" value:"169.254." |
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Account | ends_with |
| field:"Account" kind:ends_with value:"$" |
AuthenticationPackageName | eq |
| field:"AuthenticationPackageName" kind:eq value:"Kerberos" |
EventData | match |
| field:"EventData" kind:match |
EventID | eq |
| field:"EventID" kind:eq |
ServiceName | match |
| field:"ServiceName" kind:match value:"$" |
TargetUserName | contains |
| field:"TargetUserName" kind:contains value:"$" |
TargetUserName | cross_field_compare |
| field:"TargetUserName" kind:cross_field_compare |
dcount_ServiceName | is_null | field:"dcount_ServiceName" kind:is_null |
Output fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
FirstSeen | summarize |
IpAddress | summarize |
LastSeen | summarize |
TargetUserName | summarize |