BlueKeep: pre-auth RCE in RDP
A use-after-free in Remote Desktop Services allowed unauthenticated, wormable remote code execution against a huge installed base of Windows hosts.
- Severity
- 9.8critical
- Vendor
- Microsoft
- Disclosed
- 14 May 2019
- Affected
- Windows RDP (RDS)
CVSS breakdown
- Attack vector
- Network
- Attack complexity
- Low
- Privileges required
- None
- User interaction
- None
- Scope
- Unchanged
- Confidentiality
- High
- Integrity
- High
- Availability
- High
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
Analysis
BlueKeep is the outbreak that did not happen — and the reasons why are more interesting than the bug.
#The bug
Remote Desktop Services exposes virtual channels: logical sub-streams multiplexed over one RDP connection, used for clipboard, printing, drive redirection and so on.
One channel is special. MS_T120 is an internal channel that RDP sets up for its own use, and it is bound to a fixed slot — channel 31. Nothing external should ever address it.
But the server let a client request a channel named MS_T120, and bound it a second time at an attacker-chosen index. Now two references point at the same channel object with different lifetimes. When one is torn down, the object is freed; the other reference remains and is still usable.
That is a textbook use-after-free, reachable before authentication, in a kernel-mode driver (termdd.sys). Control the heap contents between the free and the reuse and you control execution — in the kernel, as SYSTEM.
Crucially, all of this happens during connection setup, before any credential is presented. RDP's authentication is irrelevant because the vulnerable code runs before it.
#Why the worm never came
Microsoft was explicit in the advisory that this was wormable, and took the rare step of patching Windows XP and Server 2003 — both long out of support. The comparison to EternalBlue was made loudly and deliberately. Roughly a million internet-facing hosts were vulnerable at disclosure.
And then… relatively little. Mass cryptomining campaigns showed up in November 2019, six months later, and mostly just crashed the hosts they hit.
The reason is instructive: reliable kernel heap grooming over RDP is genuinely difficult. The difference between a proof-of-concept that blue-screens a target and a weaponised exploit that lands cleanly across varied hardware, memory configurations and patch levels is months of engineering work. The public exploits were unreliable enough that they produced crashes more often than shells, and a crashed target is a loud target.
That is reflected in the CVSS vector for EternalBlue (AC:H) versus BlueKeep (AC:L) — and the vectors arguably have it backwards in practice. Scoring captures theoretical accessibility, not weaponisation cost.
This is worth internalising as a defender: exploitability is a spectrum, and the gap between "PoC exists" and "reliable exploit exists" can be your entire remediation window. It is also not a plan. The window closed for BlueKeep because nobody invested the effort; it does not close on request.
#Detection
- Patch state for CVE-2019-0708 across Windows 7, Server 2008 and 2008 R2, XP and Server 2003. Windows 8 and later were never affected — the channel-binding code was rewritten.
- Any RDP (3389/tcp) reachable from the internet is a finding independent of this CVE. It is the single most common initial-access vector for ransomware, usually via credential stuffing rather than a memory bug.
- Check whether NLA is enforced, not merely available. Network Level Authentication requires the client to authenticate via CredSSP before the RDP session is established, which puts the vulnerable code path out of reach of an unauthenticated attacker. This is the one mitigation that actually works pre-patch.
- Host telemetry: unexpected
termdd.syscrashes and bugchecks are the signature of an unreliable exploit attempt, not a stable one. Blue screens on RDP servers deserve investigation rather than a reboot.
#Fixing it
Patch. Then enable NLA everywhere as defence in depth — it would have blunted this bug entirely, and it blunts whatever the next pre-auth RDP bug turns out to be.
Then get RDP off the internet. Put it behind a VPN or an identity-aware proxy. If remote access must be exposed, expose something designed to be exposed.
#What generalises
The bug class is use-after-free via inconsistent object lifetime — the same object reachable through two references with different owners. It recurs constantly in C and C++ codebases that manage lifetimes by convention rather than by type.
The design lesson is sharper: an internal-only identifier (MS_T120) was trusted to be internal-only because nothing was supposed to request it. Nothing enforced that. Any name, index or handle an attacker can supply must be validated against the set that is legitimately theirs to address — "clients don't do that" is not a control.