Detection rules › Kusto

Hunt for ADWS requests from unknown devices

Group by
DeviceName, IPAddress, RemoteIP
Author
Robbe Van den Daele
Source
github.com/HybridBrothers/Hunting-Queries-Detection-Rules

This hunting rule searches for incomming ADWS connections on Domain Controllers (DC's need to be onboarded in Defender for Endpoint) from IP Addresses that cannot be linked to MDE onboarded devices.

MITRE ATT&CK coverage

References

Telemetry coverage

Rule body

let device_info = (
    // Get device network info from last 7 days
    DeviceNetworkInfo
    | where Timestamp > ago(7d)
    // Expand the IP Addresses of the devices
    | mv-expand todynamic(IPAddresses)
    | extend IPAddress = tostring(IPAddresses.IPAddress)
    // Distinct IP address for each device
    | distinct DeviceName, DeviceId, IPAddress
    // Search for each device if it is onboarded or not
    | join kind=inner (
        DeviceInfo 
        | where Timestamp > ago(7d)
        | distinct DeviceName, DeviceId, OnboardingStatus
        // Get the first timestamp the device was seen
        | join kind=inner (
            DeviceInfo
            | where Timestamp > ago(30d)
            | summarize FirstSeen = arg_min(Timestamp, DeviceId) by DeviceId
        ) on DeviceId
        | project-away DeviceId1, DeviceId2
    ) on DeviceId, DeviceName
    | project-away DeviceName1, DeviceId1
);
// Get incomming traffic on ADWS port and save unique remote IP addresses
DeviceNetworkEvents
| where Timestamp > ago(30d)
| where ActionType != "ListeningConnectionCreated"
| where InitiatingProcessFolderPath == @"c:\windows\adws\microsoft.activedirectory.webservices.exe"
| where LocalPort == "9389"
| summarize ConnectionTimes=make_list(Timestamp) by RemoteIP, DeviceName
// Get device information of remote IP addresses, results for IP we do not find information for are allowed
| join kind=leftouter device_info on $left.RemoteIP == $right.IPAddress
| project-away IPAddress
// Check if the remote IPs are onboarded devices or not
| where OnboardingStatus != "Onboarded"
// Make output better
| project DeviceName, ConnectionTimes, RemoteIP, RemoteDeviceName = DeviceName1, RemoteDeviceId = DeviceId, RemoteOnboardingStatus = OnboardingStatus, RemoteDeviceFirstSeen = FirstSeen

let device_info = (
    // Get device network info from last 7 days
    DeviceNetworkInfo
    | where TimeGenerated > ago(7d)
    // Expand the IP Addresses of the devices
    | mv-expand todynamic(IPAddresses)
    | extend IPAddress = tostring(IPAddresses.IPAddress)
    // Distinct IP address for each device
    | distinct DeviceName, DeviceId, IPAddress
    // Search for each device if it is onboarded or not
    | join kind=inner (
        DeviceInfo 
        | where TimeGenerated > ago(7d)
        | distinct DeviceName, DeviceId, OnboardingStatus
        // Get the first timestamp the device was seen
        | join kind=inner (
            DeviceInfo
            | where TimeGenerated > ago(30d)
            | summarize FirstSeen = arg_min(TimeGenerated, DeviceId) by DeviceId
        ) on DeviceId
        | project-away DeviceId1, DeviceId2
    ) on DeviceId, DeviceName
    | project-away DeviceName1, DeviceId1
);
// Get incomming traffic on ADWS port and save unique remote IP addresses
DeviceNetworkEvents
| where TimeGenerated > ago(30d)
| where ActionType != "ListeningConnectionCreated"
| where InitiatingProcessFolderPath == @"c:\windows\adws\microsoft.activedirectory.webservices.exe"
| where LocalPort == "9389"
| summarize ConnectionTimes=make_list(TimeGenerated) by RemoteIP, DeviceName
// Get device information of remote IP addresses, results for IP we do not find information for are allowed
| join kind=leftouter device_info on $left.RemoteIP == $right.IPAddress
| project-away IPAddress
// Check if the remote IPs are onboarded devices or not
| where OnboardingStatus != "Onboarded"
// Make output better
| project DeviceName, ConnectionTimes, RemoteIP, RemoteDeviceName = DeviceName1, RemoteDeviceId = DeviceId, RemoteOnboardingStatus = OnboardingStatus, RemoteDeviceFirstSeen = FirstSeen

Stages and Predicates

Let binding: device_info used in Stages 1, 8

let device_info = (
    DeviceNetworkInfo
    | where Timestamp > ago(7d)
    | mv-expand todynamic(IPAddresses)
    | extend IPAddress = tostring(IPAddresses.IPAddress)
    | distinct DeviceName, DeviceId, IPAddress
    | join kind=inner (
        DeviceInfo 
        | where Timestamp > ago(7d)
        | distinct DeviceName, DeviceId, OnboardingStatus
        | join kind=inner (
            DeviceInfo
            | where Timestamp > ago(30d)
            | summarize FirstSeen = arg_min(Timestamp, DeviceId) by DeviceId
        ) on DeviceId
        | project-away DeviceId1, DeviceId2
    ) on DeviceId, DeviceName
    | project-away DeviceName1, DeviceId1
);

Stage 1: source

let device_info

Stage 2: source

DeviceNetworkEvents

Stage 3: where

where Timestamp > ago(2592000s)

Stage 4: where

where ActionType !~ "ListeningConnectionCreated"

Stage 5: where

where InitiatingProcessFolderPath =~ @"c:\windows\adws\microsoft.activedirectory.webservices.exe"

Stage 6: where

where LocalPort == 9389

Stage 7: summarize

summarize ConnectionTimes by RemoteIP, DeviceName

Stage 8: join

join kind=leftouter (device_info) on RemoteIP, IPAddress

Stage 9: project-away

project-away IPAddress

Stage 10: where

where OnboardingStatus !~ "Onboarded"

Stage 11: project

project ConnectionTimes, DeviceName, RemoteDeviceFirstSeen, RemoteDeviceId, RemoteDeviceName, RemoteIP, RemoteOnboardingStatus

Indicators

These rows show field, operator, and value matches.

Output fields

These fields are emitted when the rule matches.

FieldSource
ConnectionTimesproject
DeviceNameproject
RemoteDeviceFirstSeenproject
RemoteDeviceIdproject
RemoteDeviceNameproject
RemoteIPproject
RemoteOnboardingStatusproject