Introduction

This blogpost is probably the first of a series that I will create in the coming months on Device Discovery. I regularly see organizations buy a specific tool to create an asset inventory list of what lives in their networks, while this is something we can actually do with Microsoft Defender for Endpoint (with some nuances). By using the tools we already have, we can save costs, and make sure that everything is as much centralized as possible in the Defender XDR asset inventory. But to be completely honest, the Microsoft tooling that provide these insights can use some improvements here and there. Because of this, I wanted to talk in this blogpost what I think could be done better, and how you can actually get these insights via some KQL queries.

The technology

Microsoft Defender for Endpoint has a specific feature capability baked in called ‘Device Discovery’. This feature uses onboarded endpoints in your network to collect, probe, or scan your network to discover unmanaged devices. This capability allows you to discover:

  • Enterprise endpoints - workstations, servers, and mobile devices
  • Network devices like routers and switches
  • eIoT devices like printers and cameras

Since I would like this blogpost to be technical as much as possible, I assume you have some basic knowledge on how MDE Device Discovery works. When this is not the case, I recommend to first read the Microsoft Learning pages if you would like to know more about what Device Discovery is and how it is configured: https://learn.microsoft.com/en-us/defender-endpoint/device-discovery

The monitored networks page

If you have worked with Device Discovery before, you will probably know the Monitored Networks page in Defender XDR. This is the page with all networks found by Device Discovery, that provides an overview of which networks are monitored and which not.

For me, there are a couple of challenges with this page for which we will find a solution later in this blogpost:

  • No possibility to search based on subnet range, default gateway, or other properties (only network name)
  • List by default only contains networks identified as ‘corporate’ networks
  • List show only 50 networks with no possibility to show more
  • List does not show which subnets are behind the network names

Interesting to note regarding this page is that:

  • The list is sorted based upon the total number of devices seen on the network in the last seven days.
  • If less than 50 ‘corporate’ networks are found, the list contains networks with the most onboarded devices.
  • Networks are marked as ‘corporate’ when multiple devices report the same network name, default gateway, and DHCP server address. These are typically chosen to be monitored automatically by Defender XDR.

I found that the most easy way to search for identified networks is by searching for them via KQL instead. Even though the GUI only show 50 networks by default, you can perfectly find and list all of the networks with a KQL query on the DeviceNetworkInfo table in Defender XDR. If you want to know which subnet ranges are connected to a specific network name, you can also find this information via KQL.

Solve the monitored networks challenges

In below query we count the amount of devices found on a specific network, where we group the networks based on their network name and DHCP server address (just like the corporate network identification but without default gateway to reduce clutter).

By doing it via KQL we can:

  • List all networks even when they are not identified as ‘corporate’ by Device Discovery
  • Show more than 50 networks
  • Find the subnets behind the network name
 1// OPTIONAL - Device cap used to ignore network with less then X devices in them
 2let device_cap = 0;
 3DeviceNetworkInfo
 4| where Timestamp > ago(7d)
 5// Ignore empty networks
 6| where ConnectedNetworks  != ""
 7// Get networks data
 8| extend ConnectedNetworksExp = parse_json(ConnectedNetworks)
 9| mv-expand bagexpansion = array ConnectedNetworks=ConnectedNetworksExp
10| extend NetworkName = tostring(ConnectedNetworks ["Name"]), NetworkDescription = tostring(ConnectedNetworks ["Description"]), NetworkCategory = tostring(ConnectedNetworks ["Category"])
11// Get subnet data for IPv4 Addresses
12| extend IPAddressesExp = parse_json(IPAddresses)
13| mv-expand bagexpansion = array IPAddresses=IPAddressesExp
14| extend IPAddress = tostring(IPAddresses ["IPAddress"]), SubnetPrefix = tolong(IPAddresses ["SubnetPrefix"])
15| extend NetworkAddress = format_ipv4(IPAddress, SubnetPrefix)
16| extend SubnetRange = strcat(NetworkAddress, "/", SubnetPrefix)
17// Exclude IPv6 and APIPPA
18| where SubnetPrefix <= 32
19| where IPAddress !startswith "169.254"
20// Ignore unidentified networks
21| where not(NetworkName has_any ("Unidentified", "Identifying..."))
22// Provide list
23| distinct DeviceId, NetworkName, IPv4Dhcp, SubnetRange
24| summarize Devices = count(), SubnetRanges = make_set(SubnetRange) by NetworkName, IPv4Dhcp
25// Ignore network with very low device count
26| where Devices >= device_cap
27| sort by Devices desc

Note

This query should give you better insights into what networks or subnets are behind the Device Discovery networks page in Defender XDR.

Once you know which subnets are linked to which network names, you can go back to the Monitored Networks page and manually change the monitored state for the networks you want Device Discovery to scan on.

Find by subnet range

If you want to know if a specific subnet in your corporate network is being monitored by Device Discovery or not, you can extend the above query to search for your subnet and check under which network names this subnet range lives:

 1// OPTIONAL - Device cap used to ignore network with less then X devices in them
 2let device_cap = 0;
 3DeviceNetworkInfo
 4| where Timestamp > ago(7d)
 5// Ignore empty networks
 6| where ConnectedNetworks  != ""
 7// Get networks data
 8| extend ConnectedNetworksExp = parse_json(ConnectedNetworks)
 9| mv-expand bagexpansion = array ConnectedNetworks=ConnectedNetworksExp
10| extend NetworkName = tostring(ConnectedNetworks ["Name"]), NetworkDescription = tostring(ConnectedNetworks ["Description"]), NetworkCategory = tostring(ConnectedNetworks ["Category"])
11// Get subnet data for IPv4 Addresses
12| extend IPAddressesExp = parse_json(IPAddresses)
13| mv-expand bagexpansion = array IPAddresses=IPAddressesExp
14| extend IPAddress = tostring(IPAddresses ["IPAddress"]), SubnetPrefix = tolong(IPAddresses ["SubnetPrefix"])
15| extend NetworkAddress = format_ipv4(IPAddress, SubnetPrefix)
16| extend SubnetRange = strcat(NetworkAddress, "/", SubnetPrefix)
17// Exclude IPv6 and APIPPA
18| where SubnetPrefix <= 32
19| where IPAddress !startswith "169.254"
20// Ignore unidentified networks
21| where not(NetworkName has_any ("Unidentified", "Identifying..."))
22// Provide list
23| distinct DeviceId, NetworkName, IPv4Dhcp, SubnetRange
24| summarize Devices = count(), SubnetRanges = make_set(SubnetRange) by NetworkName, IPv4Dhcp
25// Ignore network with very low device count
26| where Devices >= device_cap
27| sort by Devices desc
28// Search specific subnet range
29| where tostring(SubnetRanges) contains "172.16.76.0/24"

Interesting to note is that some devices report a network name with a specific number at the back of the network name:

These numbers seem to be filtered out and merged in the Device Discovery settings page:

Find networks based on documentation

In one of my previous blog posts I talked about how you can save your networking documentation in Microsoft Sentinel Watchlists, to use that information in queries later on. If you have done this, you can use the below query to correlate the subnet ranges from the watchlist with the subnet ranges from the DeviceNetworkInfo table, showing you which networks you need to configure as ‘monitored’ in Defender XDR.

 1// OPTIONAL - Device cap used to ignore network with less then X devices in them
 2let device_cap = 5;
 3// Get subnet ranges of network docs
 4let network_docs = _GetWatchlist('networks')
 5    | extend SubnetRange = strcat(NetworkAddress, "/", Prefix)
 6    | distinct Name, Location, SubnetRange;
 7// Get Networks in Defender XDR Device Discovery
 8DeviceNetworkInfo
 9| where TimeGenerated > ago(7d)
10// Ignore empty networks
11| where ConnectedNetworks  != ""
12// Get networks data
13| extend ConnectedNetworksExp = parse_json(ConnectedNetworks)
14| mv-expand bagexpansion = array ConnectedNetworks=ConnectedNetworksExp
15| extend NetworkName = tostring(ConnectedNetworks ["Name"]), NetworkDescription = tostring(ConnectedNetworks ["Description"]), NetworkCategory = tostring(ConnectedNetworks ["Category"])
16// Get subnet data for IPv4 Addresses
17| extend IPAddressesExp = parse_json(IPAddresses)
18| mv-expand bagexpansion = array IPAddresses=IPAddressesExp
19| extend IPAddress = tostring(IPAddresses ["IPAddress"]), SubnetPrefix = tolong(IPAddresses ["SubnetPrefix"])
20| extend NetworkAddress = format_ipv4(IPAddress, SubnetPrefix)
21| extend SubnetRange = strcat(NetworkAddress, "/", SubnetPrefix)
22// Join the network docs to only find networks from Defender XDR that are in the docs
23| join kind=inner network_docs on SubnetRange
24// Exclude IPv6 and APIPPA
25| where SubnetPrefix <= 32
26| where IPAddress !startswith "169.254"
27// Ignore unidentified networks
28| where not(NetworkName has_any ("Unidentified", "Identifying..."))
29// Provide list
30| distinct DeviceId, NetworkName, IPv4Dhcp, SubnetRange
31| summarize Devices = count(), SubnetRanges = make_set(SubnetRange) by NetworkName, IPv4Dhcp
32// Ignore network with very low device count
33| where Devices >= device_cap
34| sort by Devices desc

Conclusion

With above queries you should be able to get a better understanding which subnet ranges are being monitored by Device Discovery, and which networks monitoring state you need to change based on your corporate subnet ranges.