Detection rules › Kusto

Suspicious SPN logon from workstation (DumpGuard)

Author
Robbe Van den Daele
Source
github.com/HybridBrothers/Hunting-Queries-Detection-Rules

With the DumpGuard tool, attackers are able to dump credetials via Remote Credential Guard on devices that have Credential Guard enabled. Since the DumpGuard tool needs to use an SPN enabled account (in the POC they use a machine account) for two exploitation scenario's, it is interesting to look for TGT requests happening from client devices for SPN enabled accounts.

MITRE ATT&CK coverage

TacticTechniques
Credential Access

References

Telemetry coverage

Rule body

let spn_accounts = toscalar(
    // Search for all SPNs we can find in historic logs
    IdentityLogonEvents
    | where TimeGenerated > ago(14d)
    | where Application == "Active Directory"
    | where isnotempty(AdditionalFields.Spns)
    | extend Spns = split(AdditionalFields.Spns, ",")
    | summarize make_set(Spns)
);
let workstation_subnets = toscalar(
    DeviceNetworkInfo
    | where TimeGenerated > ago(14d)
    // Filter out empty device names
    | where isnotempty(DeviceName)
    // Expand IP Addresses
    | mv-expand todynamic(IPAddresses)
    // Focus on device name and IP Address info
    | distinct DeviceName, tostring(IPAddresses)
    // Filter out IPv6 addresses, /32 addresses, and APIPA addresses
    | where todynamic(IPAddresses).IPAddress !contains ":"
    | where todynamic(IPAddresses).SubnetPrefix != "32"
    | where todynamic(IPAddresses).IPAddress !startswith "169.254"
    // Find Device Type of the device
    | join kind=inner (
        DeviceInfo
        | where TimeGenerated > ago(30d)
        | distinct DeviceName, DeviceType
    ) on DeviceName
    // Only focus on workstations
    | where DeviceType == "Workstation"
    // Create Network Address based on the host IP Address and create a distinct list
    | extend NetworkAddress = format_ipv4_mask(tostring(todynamic(IPAddresses).IPAddress), tolong(todynamic(IPAddresses).SubnetPrefix))
    | summarize make_set(NetworkAddress)
);
IdentityLogonEvents
| where TimeGenerated > ago(1h)
// Get AD TGT requests by looking for Kerberos requests to KRBTGT account
| where Application == "Active Directory"
| where Protocol == "Kerberos"
| where AdditionalFields.Spns contains "krbtgt"
// Check for requests to account names with SPNs
| where AccountName in (spn_accounts)
// Check if IP Address is from a client range
| where ipv4_is_in_any_range(IPAddress, workstation_subnets)
// Optional - Ignore failed logins
| where ActionType != "LogonFailed"

Stages and Predicates

Let binding: spn_accounts used in Stage 6

let spn_accounts = toscalar(
    IdentityLogonEvents
    | where TimeGenerated > ago(14d)
    | where Application == "Active Directory"
    | where isnotempty(AdditionalFields.Spns)
    | extend Spns = split(AdditionalFields.Spns, ",")
    | summarize make_set(Spns)
);

Let binding: workstation_subnets used in Stage 7

let workstation_subnets = toscalar(
    DeviceNetworkInfo
    | where TimeGenerated > ago(14d)
    | where isnotempty(DeviceName)
    | mv-expand todynamic(IPAddresses)
    | distinct DeviceName, tostring(IPAddresses)
    | where todynamic(IPAddresses).IPAddress !contains ":"
    | where todynamic(IPAddresses).SubnetPrefix != "32"
    | where todynamic(IPAddresses).IPAddress !startswith "169.254"
    | join kind=inner (
        DeviceInfo
        | where TimeGenerated > ago(30d)
        | distinct DeviceName, DeviceType
    ) on DeviceName
    | where DeviceType == "Workstation"
    | extend NetworkAddress = format_ipv4_mask(tostring(todynamic(IPAddresses).IPAddress), tolong(todynamic(IPAddresses).SubnetPrefix))
    | summarize make_set(NetworkAddress)
);

Stage 1: source

IdentityLogonEvents

Stage 2: where

| where TimeGenerated > ago(1h)

Stage 3: where

| where Application == "Active Directory"

Stage 4: where

| where Protocol == "Kerberos"

Stage 5: where

| where AdditionalFields.Spns contains "krbtgt"

Stage 6: where

| where AccountName in (spn_accounts)

Stage 7: where

| where ipv4_is_in_any_range(IPAddress, workstation_subnets)

Stage 8: where

| where ActionType != "LogonFailed"

Indicators

These rows show field, operator, and value matches.