skip to content
← cd ..
securitydetectionkqlwindows 2026-06-14 · 7 min read

CVE-2026-41089: the Netlogon packet that reboots your domain controller

A single unauthenticated UDP packet, sent to port 389 of a domain controller, crashes LSASS and reboots the box inside a minute. No credentials, no foothold, no user interaction. That is CVE-2026-41089, and as of the start of this month it is being exploited in the wild.

The advisories call it remote code execution. The working proof of concept demonstrates a denial of service. Both framings matter, and the gap between them is the first thing worth getting straight.

The facts

FieldValue
CVECVE-2026-41089
CVSS9.8 (critical)
ClassCWE-121 stack-based buffer overflow
ComponentWindows Netlogon, CLDAP path
AccessUnauthenticated, network, no user interaction
PatchedMay 2026 Patch Tuesday (12 May)
Discovered byMicrosoft WARP (Windows Attack Research & Protection)
ExploitedYes — flagged by Belgium’s CCB; Microsoft says it cannot independently corroborate

Affected and fixed builds:

ProductFixed in
Server 2012 / 2012 R2ESU-only
Server 201610.0.14393.9140
Server 201910.0.17763.8755
Server 202210.0.20348.5074
Server 2022 23H210.0.25398.2330
Server 202510.0.26100.32772

If you have applied the May 2026 cumulative update to your domain controllers, you are done. The rest of this post is for confirming that, and for the window before everyone patches.

What the bug actually is

The flaw lives in Netlogon’s handling of CLDAP — connectionless LDAP, the UDP/389 protocol clients use to ping a DC and ask “are you a domain controller, and who are you?” The DC answers with a NlGetLocalPingResponse, serializing its server name, domain, GUIDs, and an echoed username field into a fixed 528-byte stack buffer.

The root cause is a byte/WCHAR size confusion in NetpLogonPutUnicodeString. The function is handed a maximum length in bytes but treats it as a character count. Feed it an attacker-controlled username of 130 characters — 260 bytes — and the combined writes run past the end of the buffer. One crafted CLDAP search request, one UDP packet, stack corruption.

In the public PoC the corruption lands as an LSASS crash; the DC reboots in about 60 seconds. Stack corruption of this shape is theoretically a path to SYSTEM-level code execution, which is why the score is 9.8 and the advisories say RCE. Treat it as RCE for prioritization, but know that what you will most likely observe from a commodity exploit today is a domain controller falling over and coming back.

A pre-auth packet to a core directory service that ends in DC compromise is not a new genre. Zerologon was Netlogon RPC; this is Netlogon CLDAP. The detection instinct — watch unauthenticated, connectionless traffic hitting your DCs, and watch your DCs for unexplained crash-and-reboot — outlives any single CVE.

There is a widely shared KQL detection circulating for this CVE. Its core does this:

DeviceNetworkEvents
| where ActionType == @"InboundConnectionAttempt"
| where not(ipv4_is_private(RemoteIP))
| where LocalPort == "445"
| union InboundRPCNetlogon   // RpcInterfaceUuid 12345678-1234-abcd-ef00-01234567cffb

It watches TCP/445 and the Netlogon RPC interface. That is the Zerologon shape. But CVE-2026-41089 is not RPC over SMB — it is CLDAP over UDP/389. The exploit never touches 445 and never reaches the RPC interface; it is answered by the DC’s CLDAP responder before any of that. A detection keyed on 445 and the Netlogon RPC UUID will sit quiet through the actual attack.

So the first correction is simply: look at the right port and protocol.

Detection that matches the attack path

A blunt honesty note before the queries: Microsoft Defender’s DeviceNetworkEvents records the connection metadata — source, destination, port, protocol — not the CLDAP payload. The thing that makes this exploit distinctive, an oversized username field in the search filter, is invisible to endpoint network telemetry. Catching the payload needs a network sensor (Suricata, Zeek, or a firewall that parses LDAP). What Defender can do well is scope your exposure, flag anomalous external CLDAP reaching DCs, and catch the crash-and-reboot symptom. Use all three together.

1. Exposure: vulnerable, internet-facing domain controllers

The fixed version of the exposure query — same intent as the popular one, but without the port assumption baked in:

// Internet-facing devices still carrying CVE-2026-41089
let VulnerableDCs =
    DeviceTvmSoftwareVulnerabilities
    | where CveId == "CVE-2026-41089"
    | distinct DeviceId;
DeviceInfo
| where DeviceId in (VulnerableDCs)
| where IsInternetFacing == true and isnotempty(PublicIP)
| summarize arg_max(Timestamp, DeviceName, PublicIP, OSPlatform) by DeviceId
| project DeviceName, DeviceId, PublicIP, OSPlatform

A domain controller with a public IP is the worst case, but do not stop at internet-facing — UDP/389 is reachable from anywhere on the internal network too, and that is the more realistic blast radius.

2. Anomalous CLDAP to a domain controller

This is the query the SMB/445 version should have been: inbound UDP/389 from a non-private source, against your known-vulnerable hosts.

let VulnerableDCs =
    DeviceTvmSoftwareVulnerabilities
    | where CveId == "CVE-2026-41089"
    | distinct DeviceId;
DeviceNetworkEvents
| where DeviceId in (VulnerableDCs)
| where Protocol == "Udp"
| where LocalPort == 389
| where ActionType in ("InboundConnectionAttempt", "ConnectionSuccess", "ConnectionRequest")
| where not(ipv4_is_private(RemoteIP))
| summarize Attempts = count(), Sources = make_set(RemoteIP, 50), FirstSeen = min(Timestamp), LastSeen = max(Timestamp)
    by DeviceName, DeviceId
| order by Attempts desc

Drop the ipv4_is_private exclusion and add a baseline threshold if you want to watch the internal path — legitimate CLDAP from domain members is constant, so hunt on new or unusual sources, not raw volume.

3. The symptom: LSASS crash and DC reboot

Even where you cannot see the packet, you can see the wound. A working exploit drops LSASS and bounces the DC. If your environment forwards Windows event logs into Defender (or you query them at source), correlate an LSASS application crash with an unexpected reboot on the same host within a short window:

// Requires forwarded Windows event telemetry in DeviceEvents.
// Tune ActionType/field names to your ingestion; these are the events to chase.
let Window = 5m;
let LsassCrash =
    DeviceEvents
    | where ActionType == "WindowsApplicationError"          // Application Error / Event ID 1000
    | where AdditionalFields has "lsass.exe" or AdditionalFields has "netlogon.dll"
    | project DeviceId, DeviceName, CrashTime = Timestamp;
let Reboots =
    DeviceEvents
    | where ActionType in ("UnexpectedShutdown", "SystemBootStart")   // Event IDs 6008 / 1074 / 12
    | project DeviceId, RebootTime = Timestamp;
LsassCrash
| join kind=inner Reboots on DeviceId
| where RebootTime between (CrashTime .. (CrashTime + Window))
| project DeviceName, DeviceId, CrashTime, RebootTime
| order by CrashTime desc

The event IDs (1000 for the LSASS crash naming netlogon.dll, 6008/1074 for the dirty reboot) are the durable indicators here. If you have no log forwarding, those same IDs in the DC’s local Application and System logs are the fastest manual check after an unexplained reboot.

The actual fix

Detection is the consolation prize. The fix is a patch you already have:

  1. Apply the May 2026 cumulative update to every domain controller. This is the entire mitigation. There is no registry workaround that closes the bug.
  2. Confirm with query 1 that no DC still reports the CVE — patched-then- reverted snapshots and forgotten DCs are how this survives.
  3. Do not expose CLDAP to the internet. A DC answering UDP/389 from arbitrary public IPs has bigger problems than this one CVE.
  4. Segment your DCs so internal CLDAP comes only from where it should.

Patch the DCs, prove it with the exposure query, and keep the CLDAP and crash-symptom hunts running until every controller reports clean. The packet is cheap to send; make it cheap to see.