Detection rules › Kusto
Hunt for Defender for Identity NNR issues
This query can help you in finding Network Name Resolution health issues of Microsoft Defender for Identity. NNR is a critical component which is used to get more information on IP addresses seen by MDI. Without NNR proparly working, MDI can throw a lot of False Positive alerts.
References
Event coverage
| Provider | Event/ActionType | Title |
|---|---|---|
| Sysmon | Event ID 3 | Network connection |
| Security-Auditing | Event ID 5156 | The Windows Filtering Platform has permitted a connection. |
| Security-Auditing | Event ID 5157 | The Windows Filtering Platform has blocked a connection. |
| Defender-DeviceNetworkEvents | ConnectionSuccess | Connection succeeded |
| Defender-DeviceNetworkEvents | ConnectionFailed | Connection failed |
Rule body yaml
let networks = DeviceNetworkInfo
// Expand all IPs
| mv-expand todynamic(IPAddresses)
// Get the network address related to the IP
| extend NetworkAddress = format_ipv4(tostring(IPAddresses.IPAddress), tolong(IPAddresses.SubnetPrefix))
// Build the IP with the CIDR notation
| extend IPAddress = strcat(tostring(IPAddresses.IPAddress), "/", tolong(IPAddresses.SubnetPrefix))
// Save the Prefix as an extra property
| extend Prefix = tostring(IPAddresses.SubnetPrefix)
// Make a set of all the IP's belonging to the same subnet
| summarize make_set(IPAddress) by NetworkAddress, Prefix
// Count how many IPs there are in one subnet
| extend CountIPs = array_length(set_IPAddress)
| extend Joiner = 1;
// Network Information
let network_info = DeviceNetworkInfo
| mv-expand todynamic(IPAddresses)
| extend IPAddress = tostring(IPAddresses.IPAddress);
// Ports used in NNR
let nnr_ports = dynamic(["3389", "135", "137"]);
let mdi_servers = dynamic([]);
// Query network connections
DeviceNetworkEvents
// Get events from Defender for Identity sensors - fill in mdi-servers variable for more complete results
| where InitiatingProcessFileName == "Microsoft.Tri.Sensor.exe" or DeviceName has_any (mdi_servers)
// Check traffic for NNR ports
| where RemotePort in (nnr_ports)
// Join the network info for more destination context
| join kind=inner network_info on $left.RemoteIP == $right.IPAddress
// Get distinct values
| project-rename RemoteDeviceName = DeviceName1
| distinct DeviceName, ActionType, RemoteIP, RemotePort, RemoteDeviceName
// Join all network addresses
| extend Joiner = 1
| join kind=inner networks on Joiner
// Check if remote ip is in a certain network address
| extend NetworkAddrPrefix = strcat(NetworkAddress, "/", Prefix)
| where ipv4_is_in_range(RemoteIP, NetworkAddrPrefix)
// Create Object to reuse later
| extend Obj = pack(
"DeviceName", DeviceName,
"NetworkAddrPrefix", NetworkAddrPrefix,
"RemotePort", RemotePort,
"RemoteIP", RemoteIP
)
// Count amount of failed and succeeded logins
| summarize FailedConnections = countif(ActionType == "ConnectionFailed"),
SucceededConnections = countif(ActionType == "ConnectionSuccess") by tostring(Obj)
// Extract the columns from the object again
| extend Obj = todynamic(Obj)
// Save the properties for later use
| extend DeviceName = tostring(Obj.DeviceName),
NetworkAddrPrefix = tostring(Obj.NetworkAddrPrefix),
RemotePort = tostring(Obj.RemotePort),
RemoteIP = tostring(Obj.RemoteIP)
// Create a new object to save the amount of failed and succeeded attempts per IP
| extend Obj = pack(
"RemoteIP", RemoteIP,
"SucceededConnections", SucceededConnections,
"FailedConnections", FailedConnections
)
// Create a list of the remote ips and their connections by MDI sensor, destination subnet and RemoteIP
// Subnets with only fails on both ports will fail in NNR
| summarize ConnectionDetails = make_set(Obj),
TotalSucceededConnections = sum(SucceededConnections),
TotalFailedConnections = sum(FailedConnections) by DeviceName, NetworkAddrPrefix, RemotePort
// Filter out /32 addresses
| where NetworkAddrPrefix !contains "/32"
// Sorting
| sort by TotalFailedConnections desc
// Reorder
| project-reorder DeviceName, NetworkAddrPrefix, RemotePort, TotalSucceededConnections, TotalFailedConnections, ConnectionDetails
Stages and Predicates
Parameters
let nnr_ports = dynamic(["3389", "135", "137"]);
let mdi_servers = dynamic([]);
Let binding: networks
let networks = DeviceNetworkInfo
| mv-expand todynamic(IPAddresses)
| extend NetworkAddress = format_ipv4(tostring(IPAddresses.IPAddress), tolong(IPAddresses.SubnetPrefix))
| extend IPAddress = strcat(tostring(IPAddresses.IPAddress), "/", tolong(IPAddresses.SubnetPrefix))
| extend Prefix = tostring(IPAddresses.SubnetPrefix)
| summarize make_set(IPAddress) by NetworkAddress, Prefix
| extend CountIPs = array_length(set_IPAddress)
| extend Joiner = 1;
Let binding: network_info
let network_info = DeviceNetworkInfo
| mv-expand todynamic(IPAddresses)
| extend IPAddress = tostring(IPAddresses.IPAddress);
Stage 1: source
DeviceNetworkEvents
Stage 2: where
| where InitiatingProcessFileName == "Microsoft.Tri.Sensor.exe" or DeviceName has_any (mdi_servers)
Stage 3: where
| where RemotePort in (nnr_ports)
Stage 4: join
| join kind=inner network_info on $left.RemoteIP == $right.IPAddress
Stage 5: project-rename
| project-rename RemoteDeviceName = DeviceName1
Stage 6: distinct
| distinct DeviceName, ActionType, RemoteIP, RemotePort, RemoteDeviceName
Stage 7: extend
| extend Joiner = 1
Stage 8: join
| join kind=inner networks on Joiner
Stage 9: extend
| extend NetworkAddrPrefix = strcat(NetworkAddress, "/", Prefix)
Stage 10: where
| where ipv4_is_in_range(RemoteIP, NetworkAddrPrefix)
Stage 11: extend
| extend Obj = pack(
"DeviceName", DeviceName,
"NetworkAddrPrefix", NetworkAddrPrefix,
"RemotePort", RemotePort,
"RemoteIP", RemoteIP
)
Stage 12: summarize
| summarize FailedConnections = countif(ActionType == "ConnectionFailed"),
SucceededConnections = countif(ActionType == "ConnectionSuccess") by tostring(Obj)
Stage 13: extend (3 consecutive steps)
| extend Obj = todynamic(Obj)
| extend DeviceName = tostring(Obj.DeviceName),
NetworkAddrPrefix = tostring(Obj.NetworkAddrPrefix),
RemotePort = tostring(Obj.RemotePort),
RemoteIP = tostring(Obj.RemoteIP)
| extend Obj = pack(
"RemoteIP", RemoteIP,
"SucceededConnections", SucceededConnections,
"FailedConnections", FailedConnections
)
Stage 14: summarize
| summarize ConnectionDetails = make_set(Obj),
TotalSucceededConnections = sum(SucceededConnections),
TotalFailedConnections = sum(FailedConnections) by DeviceName, NetworkAddrPrefix, RemotePort
Stage 15: where
| where NetworkAddrPrefix !contains "/32"
Stage 16: sort
| sort by TotalFailedConnections desc
Stage 17: project-reorder
| project-reorder DeviceName, NetworkAddrPrefix, RemotePort, TotalSucceededConnections, TotalFailedConnections, ConnectionDetails
Exclusions
Top-level NOT(...) conjuncts: predicates this rule actively suppresses.
| Field | Kind | Excluded values |
|---|---|---|
NetworkAddrPrefix | contains | /32 |
Indicators
Each row is a field, operator, and value that the rule matches. The corpus column counts how many other rules in the catalog look for the same combination: high numbers point to widely-used, community-vetted indicators. Blank or 1 shows that the indicator is specific to this rule.
| Field | Kind | Values |
|---|---|---|
DeviceName | match |
|
InitiatingProcessFileName | eq |
|
RemotePort | in |
|
Output fields
Fields the rule emits when it matches. Chronicle authors list these in the outcome block; they appear on the detection and $risk_score drives alerting. Sentinel / Defender XDR rules build them up through project / summarize / extend stages. Sentinel maps these into alert fields via entityMappings and customDetails; Defender XDR custom detections surface them as alert fields directly.
| Field | Source |
|---|---|
ConnectionDetails | summarize |
DeviceName | summarize |
NetworkAddrPrefix | summarize |
RemotePort | summarize |
TotalFailedConnections | summarize |
TotalSucceededConnections | summarize |