12 min read

Device isolation and containment strategies

Device isolation and containment strategies
Photo by Umanoide / Unsplash

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:

DeviceEvents
| where ActionType contains "ContainedDeviceConnectionBlocked"

If you want to get insights on which agents support MDE Containment, you can use the below query:

DeviceInfo
| where OnboardingStatus == "Onboarded" and OSPlatform contains "Windows"
| distinct DeviceId, DeviceName, ClientVersion, OSPlatform
| parse ClientVersion with Major:int "." Minor:int "." Build:int "." Revision:int
// Reference: https://learn.microsoft.com/en-us/defender-endpoint/windows-whatsnew
| extend Date = case(
    Minor >= 8760, "July-2024", 
    Minor >= 8750, "May-2024",
    Minor >= 8735, "Feb-2024",
    Minor >= 8672, "Dec-2023",
    Minor >= 8560, "Sept-2023",
    Minor > 8295, "May-2023",
    Minor == 8295 and Revision >= 1023, "May-2023",
    Minor == 8295 and Revision between (1019 .. 1023), "Jan/Feb-2023",
    Minor > 8210, "Dec-2022", 
    Minor == 8210 and Build >= 22621 and Revision >= 1016, "Dec-2022", 
    Minor == 8210 and not(Build >= 22621 and Revision >= 1016), "Aug-2022", 
    "< Aug-2022"
)
// Containment without AH Audit supported from Nov-2022
// Containment with AH Audit supported from Mar-2023
| extend Containment = case(
    Minor >= 8295, "Supported with AH Audit",
    (Minor == 8210 and Build >= 22621 and Revision >= 1016) or Minor > 8210, "Supported without AH Audit",
    "Unsupported"
)
| summarize count() by Containment
| 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:

// Gets the onboarded windows devices and checks containment support nuances
let onboardedWindows = DeviceInfo
| where OnboardingStatus == "Onboarded" and OSPlatform contains "Windows"
| distinct DeviceId, DeviceName, ClientVersion, OSPlatform
| parse ClientVersion with Major:int "." Minor:int "." Build:int "." Revision:int
// Reference: https://learn.microsoft.com/en-us/defender-endpoint/windows-whatsnew
| extend Date = case(
    Minor >= 8760, "July-2024", 
    Minor >= 8750, "May-2024",
    Minor >= 8735, "Feb-2024",
    Minor >= 8672, "Dec-2023",
    Minor >= 8560, "Sept-2023",
    Minor > 8295, "May-2023",
    Minor == 8295 and Revision >= 1023, "May-2023",
    Minor == 8295 and Revision between (1019 .. 1023), "Jan/Feb-2023",
    Minor > 8210, "Dec-2022", 
    Minor == 8210 and Build >= 22621 and Revision >= 1016, "Dec-2022", 
    Minor == 8210 and not(Build >= 22621 and Revision >= 1016), "Aug-2022", 
    "< Aug-2022"
)
// Containment without AH Audit supported from Nov-2022
// Containment with AH Audit supported from Mar-2023
| extend Containment = case(
    Minor >= 8295, "Supported with AH Audit",
    (Minor == 8210 and Build >= 22621 and Revision >= 1016) or Minor > 8210, "Supported without AH Audit",
    "Unsupported"
);
// Gets onboarded non-windows devices, since containment is not supported here
let onboardedNonWindows = DeviceInfo
| where OnboardingStatus == "Onboarded" and OSPlatform !contains "Windows"
| distinct DeviceId, DeviceName, ClientVersion, OSPlatform
| extend Containment = "Unsupported";
// Get not-onboarded Servers
let notOnboardedServers = DeviceInfo
| where OnboardingStatus != "Onboarded" and DeviceType == "Server"
| distinct DeviceId, DeviceName, ClientVersion, OSPlatform
| extend Containment = "Unsupported";
// Union all and show diagram
union onboardedNonWindows, onboardedWindows, notOnboardedServers
| summarize count() by Containment
| 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:

let isolationSupportedOS = dynamic(["Windows11", "Windows10", "WindowsServer2025", "WindowsServer2022", "WindowsServer2019", "WindowsServer2016", "WindowsServer2012R2", "Linux", "macOS"]);
let containmentSupportedOS = dynamic(["Windows11", "Windows10", "WindowsServer2025", "WindowsServer2022", "WindowsServer2019", "WindowsServer2016", "WindowsServer2012R2"]);
let base = DeviceNetworkInfo
    // Expand all IPs
    | mv-expand todynamic(IPAddresses)
    // Ignore IPv6 addresses
    | where tostring(IPAddresses.IPAddress) !contains ":"
    // Save the Prefix as an extra property and set it to /32 when empty
    | extend Prefix = iff(isnotempty(tostring(IPAddresses.SubnetPrefix)), tostring(IPAddresses.SubnetPrefix), "32");
let networks = base
    // Get network addresses with a non /32 prefix
    | where Prefix != "32"
    // Get the network address related to the IP
    | extend NetworkAddress = format_ipv4(tostring(IPAddresses.IPAddress), tolong(Prefix))
    // Build the IP and Network Address with the CIDR notation
    | extend IPAddress = strcat(tostring(IPAddresses.IPAddress), "/", Prefix)
    | extend NetworkAddress = strcat(NetworkAddress, "/", Prefix)
    // Join the Device Info information
    | join kind=inner DeviceInfo on DeviceId, ReportId
    // Ignore APIPA addresses
    | where NetworkAddress != "169.254.0.0/16"
    // Ignore merged device IDs
    | where MergedToDeviceId == ""
    // Make a set of all the Device Objects belonging to the same subnet
    | extend DeviceObj = pack(
        "DeviceName", DeviceName,
        "IPAddress", IPAddress,
        "DeviceType", DeviceType,
        "DeviceCategory", DeviceCategory,
        "IsInternetFacing", IsInternetFacing,
        "OnboardingStatus", OnboardingStatus,
        "OSDistribution", OSDistribution,
        "OSPlatform", OSPlatform
    )
    // Make a list of the objects in the same subnet
    | summarize make_set(DeviceObj) by NetworkAddress;
let device_with_host_prefix = base
    // Get network addresses with /32 Prefix to try and match other networks
    | where Prefix == "32"
    // Build the IP Address with the CIDR notation
    | extend IPAddress = strcat(tostring(IPAddresses.IPAddress), "/", Prefix)
    // Join the Device Info information
    | join kind=inner DeviceInfo on DeviceId, ReportId
    // Ignore merged device IDs
    | where MergedToDeviceId == ""
    // Make a set of all the Device Objects
    | extend DeviceObj = pack(
        "DeviceName", DeviceName,
        "IPAddress", IPAddress,
        "DeviceType", DeviceType,
        "DeviceCategory", DeviceCategory,
        "IsInternetFacing", IsInternetFacing,
        "OnboardingStatus", OnboardingStatus,
        "OSDistribution", OSDistribution,
        "OSPlatform", OSPlatform
    )
    | extend Joiner = 1;
let network_addresses = base
    // Get network addresses with a non /32 prefix
    | where Prefix != "32"
    // Get the network address related to the IP
    | extend NetworkAddress = format_ipv4(tostring(IPAddresses.IPAddress), tolong(Prefix))
    | extend NetworkAddress = strcat(NetworkAddress, "/", Prefix)
    // Create joiner to find host addresses related to certain networks
    | distinct NetworkAddress
    | extend Joiner = 1;
let networks2 = device_with_host_prefix
    // Try to join /32 IPs
    | join kind=inner network_addresses on Joiner
    // Check if IP is in the network range, and only return those IPs
    | extend InRange = ipv4_is_in_range(IPAddress, NetworkAddress)
    | where InRange == 1
    // Make a list of the objects in the same subnet
    | summarize make_set(DeviceObj) by NetworkAddress;
union networks, networks2
    // Expand the Device Objects
    | mv-expand set_DeviceObj
    // Save the DeviceType, DeviceCategory, and Onboarding Status
    | extend DeviceType = set_DeviceObj.DeviceType
    | extend DeviceCategory = set_DeviceObj.DeviceCategory
    | extend OnboardingStatus = set_DeviceObj.OnboardingStatus
    // Count how many servers, workstations, network devices, iot devices, and ot devices exists in a subnet, the onboarding estate, and OS Distribution
    | summarize Servers = countif(set_DeviceObj.DeviceType=="Server"),
        Workstations = countif(set_DeviceObj.DeviceType=="Workstation"),
        NetworkDevices = countif(set_DeviceObj.DeviceCategory=="NetworkDevice"),
        IoTDevices = countif(set_DeviceObj.DeviceCategory=="IoT"),
        OTDevices = countif(set_DeviceObj.DeviceCategory=="OT"),
        Onboarded = countif(set_DeviceObj.OnboardingStatus=="Onboarded"),
        NotOnboarded = countif(set_DeviceObj.OnboardingStatus!="Onboarded"),
        IsolateSupportedOS = countif((set_DeviceObj.OSDistribution has_any (isolationSupportedOS) or set_DeviceObj.OSPlatform == "Linux") and set_DeviceObj.OnboardingStatus == "Onboarded"),
        ContainSupportedOS = countif(set_DeviceObj.OSDistribution has_any (containmentSupportedOS) and set_DeviceObj.OnboardingStatus == "Onboarded") by NetworkAddress
    // Join the network subnets so we have the device objects again
    | join kind=leftouter networks on NetworkAddress
    | join kind=leftouter networks2 on NetworkAddress
    // Extend Array Concat
    | extend set_DeviceObj = array_concat(set_DeviceObj, set_DeviceObj1)
    // Remove duplicate columns
    | project-away NetworkAddress1, NetworkAddress2, set_DeviceObj1
    // Count how many IPs there are in one subnet
    | extend CountIPs = array_length(set_DeviceObj)
    | 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 👊.