Hardening RDP: Enforcing NLA and Detecting Sticky Keys Backdoors with WASM Tools

  • Thread Author
Remote Desktop Protocol (RDP) remains one of the most productive—and most abused—paths into Windows systems, and a recent deep-dive about Brutus’s use of WebAssembly to detect and interact with sticky‑keys backdoors highlights a practical shift in both red-team tooling and defender automation. The core lesson is simple but urgent: when Network Level Authentication (NLA) is absent, legacy accessibility features like Sticky Keys can still be abused at scale; modern toolchains such as WebAssembly (WASM) and runtimes like wazero make it easier for offensive tools to embed mature protocol stacks (e.g., IronRDP) without heavy native-build complexity, while automation lets blue teams scan thousands of targets quickly for evidence of backdoors. This article unpacks the technical claims, validates the core assertions against public protocol and vendor documentation, analyzes the trade‑offs of the WASM approach, and offers pragmatic defensive steps for Windows administrators and incident responders.

Blue cyber-security illustration featuring a Windows shield, Word icon, and RDP links.Background / Overview​

RDP exposes a Win32 graphical login flow over TCP (default 3389) and historically has been a high-value target for remote access and ransomware actors. If a server does not enforce Network Level Authentication (NLA), the RDP server will allocate a full logon desktop (the WinLogon desktop) for connecting clients before credentials are verified—giving attackers an opportunity to trigger pre-authentication accessibility shortcuts such as Sticky Keys (sethc.exe) or the Utility Manager (utilman.exe). In practice, attackers have replaced or redirected these accessibility binaries to spawn a command shell as SYSTEM and thereby bypass authentication at the logon screen; this technique has been observed by multiple incident response teams and is documented as a common technique in the ATT&CK matrix.
Layered on top of that operational fact is the engineering innovation described in the Brutus write-up: rather than porting a complex, mature RDP library into Go (a time-consuming and error-prone task), the Brutus authors compiled an existing Rust implementation (IronRDP) to WebAssembly and executed it inside a Go host via the wazero runtime. That arrangement keeps the low-level RDP protocol logic in a sandboxed module while letting Go handle sockets, TLS, and the connection lifecycle—yielding a single, statically-linked binary that can scan large target sets and run sticky‑keys checks at scale. The same article also explains two complementary detection modes: (1) a local pixel-delta heuristic that compares bitmaps before/after sending Shift scancodes, and (2) an optional AI‑assisted verification step for edge cases. The underlying problem—servers without NLA presenting a full graphical logon screen to the network—remains central to both offense and defense.

Why NLA matters (technical verification)​

What NLA actually does​

Network Level Authentication is the mechanism that stops RDP servers from instantiating the full WinLogon graphical session until the client has authenticated. Put another way, NLA moves authentication up the protocol stack and delegates it to CredSSP (the Credential Security Support Provider), which establishes an encrypted channel (TLS) and uses SPNEGO/Kerberos or NTLM to authenticate the client before the server commits user-session resources. Microsoft’s open specifications make this explicit: CredSSP uses TLS as an encrypted transport and then encapsulates GSS security tokens (Kerberos/NTLM) over that channel. This design protects the server from letting unauthenticated network peers trigger pre-authentication features that run as SYSTEM on the login screen.

Nessus / Tenable advisories and scanner flags​

Security scanners commonly flag the absence of NLA. For example, Tenable’s Nessus plugin “Terminal Services Doesn’t Use Network Level Authentication (NLA) Only” (Plugin ID 58453) reports servers that allow non-NLA negotiation—i.e., they accept full graphical connection negotiation without a prior CredSSP authentication step. That Nessus flag is an accurate and actionable indicator: if a public-facing server answers with an RDP login screen prior to NLA, an attacker can abuse pre-auth features from the network.

The “chatty” NLA / CredSSP flow — how many round trips?​

Public protocol documentation (MS‑CSSP and MS‑RDPBCGR) explains that CredSSP relies on TLS plus SPNEGO/Kerberos or NTLM exchanges; the total number of packet exchanges depends on whether Kerberos/AS/TGS steps are required (domain‑joined) or NTLM is used (workgroup). Microsoft’s specs document multiple layered handshakes—TLS setup, SPNEGO negotiation, and the underlying authentication token exchange—so NLA is more network‑bound than a single TLS handshake. However, precise claims like “nearly ten sequential network round trips to verify a single credential” are implementation-dependent and vary with environment (Kerberos vs NTLM, PKI complexity, retransmits), and I could not find an authoritative Microsoft doc that enumerates a universal “ten round trip” figure. The high-level point—that CredSSP/NLA involves several back-and-forths and thus latency makes CPU overhead (like WASM execution) comparatively negligible—is technically plausible and consistent with the documented structure of the flow, but the exact number of network round trips is not a fixed standard and should be treated as an empirical observation rather than a protocol guarantee.

WebAssembly for protocol embedding: approach, benefits, and trade-offs​

The engineering choice: embed a Rust RDP stack via WASM​

Faced with no ideal pure-Go RDP library, the Brutus authors chose to compile Devolutions’ IronRDP (a Rust implementation of the RDP protocol) into WebAssembly and ship the resulting WASM module inside the Go binary. The Go host manages network I/O, TLS handshakes, and connection lifecycle; the WASM module implements the protocol state machine and parsing/encoding logic. IronRDP is an active open-source Rust project with crates for PDUs and the RDP stack; using it in WASM avoids a large manual port and avoids CGO or native shared-library dependencies. The IronRDP project itself documents its RDP crates and usage.

Why WASM and why wazero?​

WASM gives a portable bytecode artifact that can be executed inside a sandboxed runtime. For Go projects, wazero is an attractive choice because it is a zero‑dependency, pure‑Go WebAssembly runtime—no cgo, no external toolchains—so you can compile, embed, and ship a static Go binary that still runs Rust-compiled WASM modules. The wazero documentation and community pages describe exactly this pattern: embedding WASM to avoid native runtime dependencies and keep deployment simple. Benchmarks and production reports show that while WASM runtimes introduce some overhead, that overhead is often dwarfed by network or I/O latency in network protocols. For an RDP scanner that spends most of its time waiting on network round trips (especially in NLA flows), the WASM interpreter cost is comparatively small.

Benefits​

  • Reduced maintenance: leverage a mature, tested protocol implementation instead of porting.
  • Cross-platform binary: a single, statically-linked Go binary that runs across platforms without CGO.
  • Sandboxing: WASM’s sandbox reduces risk from bugs in the bundled protocol code.
  • Ease of distribution: lightweight vendor artifact—no platform-specific shared libraries to distribute.

Trade-offs and risks​

  • Performance ceiling: WASM execution is not as fast as native Rust or C in some hot paths; while network latency often dominates, microbenchmarks show WASM can be slower—plan capacity accordingly.
  • Tooling complexity: build chains that compile Rust -> WASM must be reproducible and tested across targets.
  • Threat-model blindspots: embedding third‑party protocol logic means the host must carefully control memory/timeouts and validate all inputs; while WASM sandboxing helps, it does not replace careful integration testing.
  • Supply-chain and licensing: bundling third‑party code raises license compliance and update requirements.

Sticky‑keys detection: heuristics, AI, and operational scale​

Two-layer detection strategy​

The Brutus design described two complementary detection modes:
  • Local pixel‑delta heuristic: capture a baseline bitmap of the login screen, then send five Shift scancodes (the Sticky Keys trigger), capture a second bitmap, and check for a rectangular region of changed pixels consistent with a terminal window (size, fill ratio, pixel delta). This is low-dependency and can run fully offline against captured bitmaps.
  • AI‑assisted confirmation (optional): if enabled, send the post-trigger bitmap to an image recognition API (the implementation example used Claude’s Vision API) to visually confirm whether a terminal window (cmd.exe, PowerShell) is present. This catches unusual shells or mis-sized windows the heuristic might miss.
Operationally this is useful because manual testing at scale is untenable: tens of thousands of internet- or VPN-exposed RDP endpoints cannot all be checked by hand. Tools like Sticky‑Keys‑Slayer originated to fill that automation gap, but older implementations are often bash/POSIX-oriented and carry more dependencies—embedding detection into a single binary simplifies large assessments.

Accuracy and false positives​

Pixel-delta heuristics are fast and dependency‑free but potentially brittle: custom shells, localized OS themes, or overlapping UI elements can cause false positives or false negatives. The optional AI layer increases confidence but introduces privacy and operational considerations (sending screenshots to a third‑party API). Both approaches require calibration and conservative thresholds for production scanning.
Best practice: run fast heuristics first to triage candidates, then verify high‑confidence positives with a manual check or a privacy‑compliant, on‑premise image classifier.

Exploitation and convenience features: built-in browser RDP​

A useful operational feature for offensive operators (and, in controlled assessments, defenders exercising purple‑team scenarios) is a browser‑based RDP client: if you find a vulnerable/sticky‑keyed endpoint, launching an embedded RDP session accessible via web browser avoids client-side installation hassles and jump-box friction. Brutus’s built‑in web client and SSH port‑forward friendly design are conveniences that make interactive investigation and containment easier for testers—however, defenders should be aware attackers could adopt similar conveniences for rapid exploitation. The presence of such features underscores why defensive controls and detection matter at scale. (The Security Boulevard write-up specifically highlights the ability to spawn an embedded RDP client from the scanning host.)

Defensive recommendations (practical, prioritized)​

  • Require NLA everywhere
  • Set “Require user authentication for remote connections by using Network Level Authentication” on RD Session Hosts and endpoints exposed to remote access. If you use group policy, enforce it at the OU level. This blocks the entire class of pre-auth accessibility backdoors when properly applied. Microsoft’s RDP docs and configuration guides explicitly recommend NLA/CredSSP for this reason.
  • Detect accessibility‑binary tampering
  • Monitor for changes to system accessibility binaries (sethc.exe, utilman.exe, osk.exe, magnify.exe) and for Image File Execution Options (IFEO) Debugger registry modifications that redirect those binaries. MITRE and CISA detection guidance explicitly recommend monitoring both file integrity and IFEO registry keys as a detection control for these backdoors. Implement file integrity monitoring (e.g., using OS‑native APIs, Sysmon file creation events, or third‑party EDR/FIM) and watch for debugger registry additions.
  • Harden external exposure
  • Don’t expose RDP directly to the Internet. Use multi‑factor VPNs, bastion hosts, or conditional access products. Apply rate limits and account lockout policies to reduce credential stuffing and brute-force success.
  • Log and retain RDP activity
  • Capture and retain RDP session and logon events (including winlogon and session creation events) for at least 90 days when possible. Centralized logs accelerate detection and post‑incident analysis; many security advisories recommend keeping RDP logs for extended periods.
  • Scan for backdoors at scale
  • Use or build automation to check for sticky‑keys backdoors: scan for IFEO debugger keys and file hash anomalies, or run a safe, read‑only RDP connection that captures the logon bitmap and performs a detection heuristic. Tools like Sticky‑Keys‑Slayer and community scripts illustrate this approach, but production implementations should use hardened, vetted tooling and respect engagement rules for penetration tests.
  • Patch and minimize attack surface
  • Apply latest OS and VPN patches. Where accessible machines must be externally reachable, follow the principle of least exposure: limit remote desktop to authenticated host networks and use network segmentation.

Red‑team / defender tradecraft and ethics​

  • Red teams should treat automation responsibly: large-scale scans that trigger accessibility features can create privacy, stability, and service‑impact risks (e.g., starting heavy WinLogon sessions on many servers). Always obtain explicit scope and rules of engagement before running large scans.
  • Blue teams should be prepared to validate any automated findings. Heuristics and even AI‑backed confirmation steps can be wrong—make verification a built-in, human review step.
  • Tool authors embedding third‑party protocol stacks must maintain update processes: the bundled WASM modules (IronRDP or others) must be tracked for security fixes and patched accordingly.

Critical analysis: strengths, limitations, and open questions​

Strengths​

  • The WASM-in-Go approach is an elegant compromise: it preserves a Go-centric deployment model while letting maintainers leverage mature, protocol-complete implementations written in other languages. For security tooling where build reproducibility matters, using a pure‑Go runtime like wazero reduces friction and avoids CGO/popular packaging nightmares.
  • Automation of sticky‑keys detection addresses a real defender gap: manual checks do not scale, and tools with heavy dependencies are hard to run from Windows jump boxes or ephemeral test hosts. A single static binary that can run across platforms removes much of that friction.

Limitations & risks​

  • WASM performance is bounded by the runtime and the host environment. While RDP’s network latency often dominates, high‑throughput scanning or interactive sessions may expose execution and memory limits; operators must benchmark their own workloads.
  • The detection heuristics rely on observable UI changes. Environments with custom shells, nonstandard themes, or enforced display scaling could generate both false positives and false negatives. The optional AI confirmation helps but raises privacy and operational governance questions.
  • The claim that NLA requires “nearly ten sequential network round trips” cannot be treated as a protocol invariant. RDP / CredSSP involves multiple layered exchanges (TLS + SPNEGO + Kerberos/NTLM) and can be chatty in many deployments, but the exact number of round trips depends on environment and the chosen authentication mechanism. I could not locate a Microsoft specification that enumerates a canonical “ten” round‑trip count—treat that as an empirical observation rather than protocol truth.

Open questions for defenders​

  • Should enterprises allow image‑based, AI‑backed offload to cloud classifiers for detection? That depends on privacy policy and data‑sovereignty constraints; on‑prem classifiers are safer but costlier.
  • How to operationalize WASM‑embedded third‑party stacks in regulated environments? Organizations must define processes for supply‑chain scanning, SBOM tracking, and rapid patching of embedded modules.

Step‑by‑step checklist for remediation (quick action list)​

  • Enforce NLA for all RDP endpoints via Group Policy or local policy.
  • Audit IFEO Debugger keys for accessibility binaries and remediate any non‑standard values. Monitor registry change events.
  • Deploy file integrity monitoring for system32 accessibility binaries and alert on altered hashes or unexpected file replacements.
  • Remove public exposure of RDP where possible; if necessary, restrict access behind MFA VPNs and hardened jump-boxes.
  • Run a controlled scan (or engage a test team) to enumerate endpoints that present a WinLogon desktop pre-authentication and prioritize them for patching. Use vetted automation or orchestrated manual checks.

Conclusion​

The interplay between legacy Windows accessibility features and modern tooling is a reminder that old primitives remain relevant vectors. Sticky‑keys backdoors are not a new technique, but the ability to detect and interact with them at scale for both red and blue teams has matured: WebAssembly enables cross‑language reuse of protocol stacks like IronRDP, and pure‑Go runtimes like wazero make packaging and deployment simpler. Defenders should prioritize NLA enforcement, monitor accessibility binaries and IFEO registry keys, and adopt scalable detection workflows—balancing automation with careful verification and privacy controls. Finally, bear in mind that some of the performance and round‑trip observations are empirical and environment-dependent; use the cited Microsoft protocol specs and vendor advisories as the source of truth when configuring and hardening RDP deployments.

Notes: the problem of accessibility‑feature backdoors is well‑documented by incident response teams and security authorities, and automation tools and scripts (for example, community utilities that scan for sethc.exe/utilman.exe tampering) exist to help defenders triage large fleets. If you manage Windows remote access, make NLA non‑negotiable and instrument your environment to detect tampering of pre‑logon binaries—those two steps dramatically reduce the attack surface exploited by sticky‑keys backdoors.

Source: Security Boulevard Et Tu, RDP? Detecting Sticky Keys Backdoors with Brutus and WebAssembly
 

Back
Top