
Device isolation and containment strategies
Introduction
As a Security Operation Center, you want to be able to contain devices and users on a network as a response to an adversary event. However, depending on the security stack you are using, containing a device can be done using multiple ways. Each way of performing a containment action can bring benefits from another, and sometimes it is hard to understand which option is the best one to use.
In this blogpost I wanted to talk about the device containment features Microsoft Defender for Endpoint provides, and which feature you should use in which scenarios. Extending on the Defender for Endpoint features, I wanted to add other ways of performing device isolations using Networking Infrastructure with a a corporate firewall or a NAC solution. There are some other technologies you can use as well, which I will cover in a later post.
In this blog I will explain the features using a scenario, for which we will use a traditional on-premise network setup that you will see in a lot of organizations. This setup has a hub-spoke model, where all subnets are routed to the core firewall in the network. In this example, we will have the following subnets:
- MDE Server Subnet - Containing servers that are all MDE onboarded
- Non-MDE Server Subnet - Containing servers that are not MDE onboarded
- Wired Client Subnet - Containing endpoint devices with a wired connection
- Wireless Client Subnet - Containing endpoint devices connected via WiFi
Microsoft-managed device
Before we go to the advanced containment scenarios, we first need to know which containment features Defender for Endpoint has. In this section, you will read all about these features.
Device isolation via MDE
Feature
The first and probably best known action, is to isolate an MDE onboarded device. This feature disconnects the compromised device from the network while retaining connectivity to the Defender for Endpoint service. The connectivity to the Defender for Endpoint service is of course needed to un-isolate the device again, and to be able to keep monitoring the device.
OS Support
Isolating a device is currently available for
- Windows Clients (starting from Windows 10, version 1703)
- Windows Servers (starting from Windows Server 2012R2)
- Linux
- MacOS
Although you will need to comply with a couple of prerequisites specifically for Linux and MacOS. More information can be found at Microsoft Learn.
For Windows 10 clients version 1709 or later and Windows 11 clients, you can also choose to enable Outlook, Microsoft Teams, and Skype for Business connectivity (a.k.a ‘Selective Isolation’). This can help in scenarios where the end-user still needs to be able to reach out to colleagues during the device isolation.
At the time of writing, selective isolation only works on the classic versions of Outlook and Microsoft Teams!
Restrict app execution in MDE
Feature
If you want to lock down a device and prevent subsequent attempts of potentially malicious programs from running, you can use the ‘Restrict app execution’ feature in Defender for Endpoint. This feature uses Windows Defender Application Control code integrity policies, to only allow files to run if they are signed by a Microsoft-issued certificate.
This option is a bit more flexible than the isolate option, since it still allows for more programs to run. In this case, users will still be able to use the full O365 stack for example. With this feature, most of the traffic to other devices in the network will be blocked, but not all of it. I would recommend to only use this feature in a scenario where the device is not connected to a corporate network.
OS Support
This feature is available on:
- Windows Clients (Starting from Windows 10)
- Windows Servers (Starting from Windows Server 2019)
Contain user in MDE
Feature
When you want to contain a specific user rather than a device, you can use Defender for Endpoint to deny incoming traffic in specific protocols related to that user (such as RPC, SMB, and RDP). This feature also terminates ongoing remote sessions and logoff existing RDP connections.
Unfortunately, containing users is currently only available automatically by using automatic attack disruption in Defender XDR. When a Contain User action is being enforced on a Domain Controller, a ‘Contain User’ policy is automatically set in the Default Domain Controller GPO.
OS Support
This feature is supported for:
- Windows Clients (Starting from Windows 10)
- Windows Servers (Starting from Windows Server 2019)
- Windows Server 2012R2 and 2016 with the modern agent
Non Microsoft-managed device
In a lot of environments, there are network devices that cannot be onboarded in Defender for Endpoint (IoT devices, unsupported OS, network equipment, etc.). For these devices, we have multiple strategies we can use for containment.
Device containment via MDE (not-onboarded devices)
Feature
When you have identified an unmanaged device that is compromised or potentially compromised, you might want to contain that device from the network to prevent the potential attack from moving laterally across the network. This can be accomplished by the ‘Contain device’ feature in Microsoft Defender for Endpoint. This feature makes sure that onboarded devices will block incoming and outgoing communication with that device.
OS Support
Blocking incoming and outgoing communication with a ‘contained’ device is supported on onboarded Microsoft Defender for Endpoint devices with the following versions:
- Windows Server 2019+
- Windows 10+
- Windows Server 2012R2 and 2016 with unified agent
However, the support is a bit more nuanced than the OS version alone. The MDE onboarded devices must have an updated agent after the following dates:
- November 2022 (will contain but without Advanced Hunting logging)
- March 2023 (will contain with Advanced Hunting logging)
All these nuances are not yet described in the Microsoft documentation, but after flagging this to Microsoft they should fix this soon.
For devices with the agent update of March 2023, you can find communications blocked with a contained device using the following query:
1DeviceEvents
2| where ActionType contains "ContainedDeviceConnectionBlocked"
If you want to get insights on which agents support MDE Containment, you can use the below query:
1DeviceInfo
2| where OnboardingStatus == "Onboarded" and OSPlatform contains "Windows"
3| distinct DeviceId, DeviceName, ClientVersion, OSPlatform
4| parse ClientVersion with Major:int "." Minor:int "." Build:int "." Revision:int
5// Reference: https://learn.microsoft.com/en-us/defender-endpoint/windows-whatsnew
6| extend Date = case(
7 Minor >= 8760, "July-2024",
8 Minor >= 8750, "May-2024",
9 Minor >= 8735, "Feb-2024",
10 Minor >= 8672, "Dec-2023",
11 Minor >= 8560, "Sept-2023",
12 Minor > 8295, "May-2023",
13 Minor == 8295 and Revision >= 1023, "May-2023",
14 Minor == 8295 and Revision between (1019 .. 1023), "Jan/Feb-2023",
15 Minor > 8210, "Dec-2022",
16 Minor == 8210 and Build >= 22621 and Revision >= 1016, "Dec-2022",
17 Minor == 8210 and not(Build >= 22621 and Revision >= 1016), "Aug-2022",
18 "< Aug-2022"
19)
20// Containment without AH Audit supported from Nov-2022
21// Containment with AH Audit supported from Mar-2023
22| extend Containment = case(
23 Minor >= 8295, "Supported with AH Audit",
24 (Minor == 8210 and Build >= 22621 and Revision >= 1016) or Minor > 8210, "Supported without AH Audit",
25 "Unsupported"
26)
27| summarize count() by Containment
28| render piechart
Containment nuances
The nuance of device containment only being supported for specific operating systems with the correct MDE agent versions is very important to me. This because a lot of organizations still have Linux and MacOS devices, and servers running with a legacy OS. If this is the case in your environment as well, you might want to look at other ways to contain an unmanaged device (as discussed in the following sections).
Nevertheless, if all of your important crown jewels in your network are MDE containment capable, Defender for Endpoint containment is sufficient to protect your assets from an unmanaged device. If you want to know your containment capability estate (including not onboarded / non-windows servers), you can use the below query:
1// Gets the onboarded windows devices and checks containment support nuances
2let onboardedWindows = DeviceInfo
3| where OnboardingStatus == "Onboarded" and OSPlatform contains "Windows"
4| distinct DeviceId, DeviceName, ClientVersion, OSPlatform
5| parse ClientVersion with Major:int "." Minor:int "." Build:int "." Revision:int
6// Reference: https://learn.microsoft.com/en-us/defender-endpoint/windows-whatsnew
7| extend Date = case(
8 Minor >= 8760, "July-2024",
9 Minor >= 8750, "May-2024",
10 Minor >= 8735, "Feb-2024",
11 Minor >= 8672, "Dec-2023",
12 Minor >= 8560, "Sept-2023",
13 Minor > 8295, "May-2023",
14 Minor == 8295 and Revision >= 1023, "May-2023",
15 Minor == 8295 and Revision between (1019 .. 1023), "Jan/Feb-2023",
16 Minor > 8210, "Dec-2022",
17 Minor == 8210 and Build >= 22621 and Revision >= 1016, "Dec-2022",
18 Minor == 8210 and not(Build >= 22621 and Revision >= 1016), "Aug-2022",
19 "< Aug-2022"
20)
21// Containment without AH Audit supported from Nov-2022
22// Containment with AH Audit supported from Mar-2023
23| extend Containment = case(
24 Minor >= 8295, "Supported with AH Audit",
25 (Minor == 8210 and Build >= 22621 and Revision >= 1016) or Minor > 8210, "Supported without AH Audit",
26 "Unsupported"
27);
28// Gets onboarded non-windows devices, since containment is not supported here
29let onboardedNonWindows = DeviceInfo
30| where OnboardingStatus == "Onboarded" and OSPlatform !contains "Windows"
31| distinct DeviceId, DeviceName, ClientVersion, OSPlatform
32| extend Containment = "Unsupported";
33// Get not-onboarded Servers
34let notOnboardedServers = DeviceInfo
35| where OnboardingStatus != "Onboarded" and DeviceType == "Server"
36| distinct DeviceId, DeviceName, ClientVersion, OSPlatform
37| extend Containment = "Unsupported";
38// Union all and show diagram
39union onboardedNonWindows, onboardedWindows, notOnboardedServers
40| summarize count() by Containment
41| render piechart
Device containment via firewall
Firewall containment
In this setup, an unmanaged device in a subnet which is routed to the firewall can be contained by creating a firewall rule that blocks all traffic to and from the device, effectively protecting the devices in the other subnets.
The caveat here is that the unmanaged device will still be able to speak to the other devices within that same ‘Wired Client Subnet’ range. This is because this traffic is destined for devices in the same subnet, which means this will not traverse the firewall.
Firewall + MDE containment
So what if you want to block both traffic going to other subnets via the firewall, and traffic in the same subnet going to managed devices? In this case I would recommend to combine the Firewall Isolation with the Defender for Endpoint ‘Contain device’ feature, which will make sure you can protect your supported MDE onboarded devices in the same subnet as well.
But what if there is another device in that same subnet that is not managed or does not support the MDE Contain feature as discussed earlier? In that case, we need to contain the device via the networking switches if we do not want to allow that traffic either. This scenario we will discuss in the next chapter.
Device isolation via Network Access Control or Access Control Lists
Network Access Control (NAC) is a ‘feature’ supported by the leading switch vendors that let you control which devices are able to connect to a network. It authenticates the devices, and can decide based on the authorization profiles you define in which subnet the device should belong to. Access Control Lists (ACL) are rule you can define on certain switches, that allow you to decide which devices can communicate with each other in the same subnet.
These principles are the key to solving the latest problem statement, where we want to contain an unmanaged device from other unmanaged devices in the same subnet. By using a NAC policy that ‘for example’, shuts the port or places the potentially malicious device in an isolation VLAN.
Another reason why you might want to use isolations via NAC, is when the potentially malicious devices switches MAC addresses and IP addresses. When this happens, isolations on firewalls will not be sufficient anymore, forcing you to isolate the devices on the switches or Access Points.
Wireless client isolation
Some vendors provide the feature for Wireless Client Isolations, which means that devices connected on the same Access Point are prevented from communicating with each other. This feature can come in handy when you want to prevent an unmanaged device from communicating to other devices. However, this might impact legitimate traffic of other devices as well.
Conclusion
So what strategy should you use after reading this? The first question you need to ask yourself is if all of your devices (or at least your business critical devices) support Microsoft Defender for Endpoint Isolation and Containment or not. When this is the case, you can perfectly rely on Defender for Endpoint solely. If you want to investigate if certain subnets contain not-onboarded devices or devices that probably do not support isolation or containment via MDE, you can use below query:
1let isolationSupportedOS = dynamic(["Windows11", "Windows10", "WindowsServer2025", "WindowsServer2022", "WindowsServer2019", "WindowsServer2016", "WindowsServer2012R2", "Linux", "macOS"]);
2let containmentSupportedOS = dynamic(["Windows11", "Windows10", "WindowsServer2025", "WindowsServer2022", "WindowsServer2019", "WindowsServer2016", "WindowsServer2012R2"]);
3let base = DeviceNetworkInfo
4 // Expand all IPs
5 | mv-expand todynamic(IPAddresses)
6 // Ignore IPv6 addresses
7 | where tostring(IPAddresses.IPAddress) !contains ":"
8 // Save the Prefix as an extra property and set it to /32 when empty
9 | extend Prefix = iff(isnotempty(tostring(IPAddresses.SubnetPrefix)), tostring(IPAddresses.SubnetPrefix), "32");
10let networks = base
11 // Get network addresses with a non /32 prefix
12 | where Prefix != "32"
13 // Get the network address related to the IP
14 | extend NetworkAddress = format_ipv4(tostring(IPAddresses.IPAddress), tolong(Prefix))
15 // Build the IP and Network Address with the CIDR notation
16 | extend IPAddress = strcat(tostring(IPAddresses.IPAddress), "/", Prefix)
17 | extend NetworkAddress = strcat(NetworkAddress, "/", Prefix)
18 // Join the Device Info information
19 | join kind=inner DeviceInfo on DeviceId, ReportId
20 // Ignore APIPA addresses
21 | where NetworkAddress != "169.254.0.0/16"
22 // Ignore merged device IDs
23 | where MergedToDeviceId == ""
24 // Make a set of all the Device Objects belonging to the same subnet
25 | extend DeviceObj = pack(
26 "DeviceName", DeviceName,
27 "IPAddress", IPAddress,
28 "DeviceType", DeviceType,
29 "DeviceCategory", DeviceCategory,
30 "IsInternetFacing", IsInternetFacing,
31 "OnboardingStatus", OnboardingStatus,
32 "OSDistribution", OSDistribution,
33 "OSPlatform", OSPlatform
34 )
35 // Make a list of the objects in the same subnet
36 | summarize make_set(DeviceObj) by NetworkAddress;
37let device_with_host_prefix = base
38 // Get network addresses with /32 Prefix to try and match other networks
39 | where Prefix == "32"
40 // Build the IP Address with the CIDR notation
41 | extend IPAddress = strcat(tostring(IPAddresses.IPAddress), "/", Prefix)
42 // Join the Device Info information
43 | join kind=inner DeviceInfo on DeviceId, ReportId
44 // Ignore merged device IDs
45 | where MergedToDeviceId == ""
46 // Make a set of all the Device Objects
47 | extend DeviceObj = pack(
48 "DeviceName", DeviceName,
49 "IPAddress", IPAddress,
50 "DeviceType", DeviceType,
51 "DeviceCategory", DeviceCategory,
52 "IsInternetFacing", IsInternetFacing,
53 "OnboardingStatus", OnboardingStatus,
54 "OSDistribution", OSDistribution,
55 "OSPlatform", OSPlatform
56 )
57 | extend Joiner = 1;
58let network_addresses = base
59 // Get network addresses with a non /32 prefix
60 | where Prefix != "32"
61 // Get the network address related to the IP
62 | extend NetworkAddress = format_ipv4(tostring(IPAddresses.IPAddress), tolong(Prefix))
63 | extend NetworkAddress = strcat(NetworkAddress, "/", Prefix)
64 // Create joiner to find host addresses related to certain networks
65 | distinct NetworkAddress
66 | extend Joiner = 1;
67let networks2 = device_with_host_prefix
68 // Try to join /32 IPs
69 | join kind=inner network_addresses on Joiner
70 // Check if IP is in the network range, and only return those IPs
71 | extend InRange = ipv4_is_in_range(IPAddress, NetworkAddress)
72 | where InRange == 1
73 // Make a list of the objects in the same subnet
74 | summarize make_set(DeviceObj) by NetworkAddress;
75union networks, networks2
76 // Expand the Device Objects
77 | mv-expand set_DeviceObj
78 // Save the DeviceType, DeviceCategory, and Onboarding Status
79 | extend DeviceType = set_DeviceObj.DeviceType
80 | extend DeviceCategory = set_DeviceObj.DeviceCategory
81 | extend OnboardingStatus = set_DeviceObj.OnboardingStatus
82 // Count how many servers, workstations, network devices, iot devices, and ot devices exists in a subnet, the onboarding estate, and OS Distribution
83 | summarize Servers = countif(set_DeviceObj.DeviceType=="Server"),
84 Workstations = countif(set_DeviceObj.DeviceType=="Workstation"),
85 NetworkDevices = countif(set_DeviceObj.DeviceCategory=="NetworkDevice"),
86 IoTDevices = countif(set_DeviceObj.DeviceCategory=="IoT"),
87 OTDevices = countif(set_DeviceObj.DeviceCategory=="OT"),
88 Onboarded = countif(set_DeviceObj.OnboardingStatus=="Onboarded"),
89 NotOnboarded = countif(set_DeviceObj.OnboardingStatus!="Onboarded"),
90 IsolateSupportedOS = countif((set_DeviceObj.OSDistribution has_any (isolationSupportedOS) or set_DeviceObj.OSPlatform == "Linux") and set_DeviceObj.OnboardingStatus == "Onboarded"),
91 ContainSupportedOS = countif(set_DeviceObj.OSDistribution has_any (containmentSupportedOS) and set_DeviceObj.OnboardingStatus == "Onboarded") by NetworkAddress
92 // Join the network subnets so we have the device objects again
93 | join kind=leftouter networks on NetworkAddress
94 | join kind=leftouter networks2 on NetworkAddress
95 // Extend Array Concat
96 | extend set_DeviceObj = array_concat(set_DeviceObj, set_DeviceObj1)
97 // Remove duplicate columns
98 | project-away NetworkAddress1, NetworkAddress2, set_DeviceObj1
99 // Count how many IPs there are in one subnet
100 | extend CountIPs = array_length(set_DeviceObj)
101 | sort by CountIPs desc
This query is rather big and can probably be optimized a bit. The result will in most cases contain a supernet, which you will have to filter out yourself if needed.
The IsolateSupportedOS and ContainSupportedOS columns are calculated based on OS only. The correct agent version nuances as discussed earlier are not yet added.
As an example you can find an organization below where we can perfectly rely on Defender for Endpoint for containment and isolation:
When the above is not true for your organization, you will need to integrate other networking products in your isolation and containment strategy. The most important question to ask here is if unmanaged devices can live in the same subnets as your managed devices or not. If not, isolation via firewall should be sufficient in your case, but when this is the case, you will need to integrate your switches, access points, or NAC solution.
To help you decide which feature to use, I listed the table below:
In a later blog post, I will explain how we can integrate third-party networking solution for your isolation and containment scenario’s with Microsoft Sentinel.