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
| Field | Value |
|---|---|
| CVE | CVE-2026-41089 |
| CVSS | 9.8 (critical) |
| Class | CWE-121 stack-based buffer overflow |
| Component | Windows Netlogon, CLDAP path |
| Access | Unauthenticated, network, no user interaction |
| Patched | May 2026 Patch Tuesday (12 May) |
| Discovered by | Microsoft WARP (Windows Attack Research & Protection) |
| Exploited | Yes — flagged by Belgium’s CCB; Microsoft says it cannot independently corroborate |
Affected and fixed builds:
| Product | Fixed in |
|---|---|
| Server 2012 / 2012 R2 | ESU-only |
| Server 2016 | 10.0.14393.9140 |
| Server 2019 | 10.0.17763.8755 |
| Server 2022 | 10.0.20348.5074 |
| Server 2022 23H2 | 10.0.25398.2330 |
| Server 2025 | 10.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.
Why the popular detection misses
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:
- Apply the May 2026 cumulative update to every domain controller. This is the entire mitigation. There is no registry workaround that closes the bug.
- Confirm with query 1 that no DC still reports the CVE — patched-then- reverted snapshots and forgotten DCs are how this survives.
- Do not expose CLDAP to the internet. A DC answering UDP/389 from arbitrary public IPs has bigger problems than this one CVE.
- 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.