core-jmp core-jmpdeath of core jump

Signed, Sealed, and Relayed, NTLM-style: Bypassing LDAP Signing Through Reauthentication

Raj Patel (SpecterOps, 23 Oct 2026) shows how an attacker who already owns a signed LDAP session can relay a coerced Windows SMB client and stack that victim bind with an LDAP write in one SASL record. The DC verifies the MAC under the attacker key, then runs the modify as the victim. Fixed in KB5126646; related SMB issue in KB5122882.

oxfemale November 5, 2026 30 min read 64 reads
Export PDF
Signed, Sealed, and Relayed, NTLM-style: Bypassing LDAP Signing Through Reauthentication
Original text: “Signed, Sealed, and Relayed, NTLM-style: Bypassing LDAP Signing Through Reauthentication” — Raj Patel, SpecterOps (23 October 2026). Code, tables and figures below are reproduced with attribution captions.

Executive Summary

For a decade the standard answer to NTLM relay against a domain controller was LDAP signing. If the DC refused unsigned LDAP, a relay that did not know the victim’s session key could not speak after the bind. In October 2026 Raj Patel at SpecterOps published the reason that answer stopped being enough. An attacker who already owned a signed LDAP session could keep that session key, coerce a Windows SMB client, and drop the victim’s final NTLM AUTHENTICATE into the same SASL record as a ModifyRequest. The DC checked the signature once, with the attacker’s key, then ran the modify as the victim. Signing was still on. The signature was just computed with the wrong identity’s key.

The path is ugly in the useful sense. A machine account coerced over SMB — including a domain controller talking to another domain controller — can be turned into resource-based constrained delegation or shadow credentials on LDAP, then into DCSync. Patel’s lab DC was Windows Server 2025 build 26100.33451 with the September 2026 cumulative update still installed. Every server from 2016 through 2025 that he tried accepted the stacked record. Microsoft shipped KB5126646 on 13 October 2026; the related SMB issue had already gone out quietly in September as KB5122882. The public wrapper is LDAP Switcheroo on top of Impacket’s ntlmrelayx. This write-up follows Patel’s order: why signing was supposed to hold, how NTLMv2 keys actually work, the SASL stacking trick, the Wireshark walkthrough, LDAP_CONN::ProcessRequestEx, and the identity counter in the patch.

Two identities, one signature

NTLM relay is the act of taking someone else’s authentication handshake and finishing it against a service you choose. The client thinks it is talking to a file share. The domain controller thinks it is talking to that client. You sit in the middle and never learn the password. What you get is whatever that client was allowed to do on the service you picked.

Signing was meant to break that. After a successful NTLM bind, every later LDAP message has to carry a MAC that only the two ends of that session can compute. A relay that does not know the victim’s exported session key cannot produce that MAC, so the DC drops the traffic. Patel’s observation is narrower and worse: the DC verified the MAC against the session that was already on the socket, then changed who the socket was, then kept reading. The MAC was valid for identity X. The modify ran as identity Y.

For years, we thought LDAP and SMB signing protected us from NTLM relay attacks. However, we found that both protocols could be relay targets through re-authentication on a connection the attacker already owned. Signing was still enforced, just with the attacker known session key.

Raj Patel, SpecterOps, 23 October 2026

If you run Active Directory and you already ticked “LDAP signing required” in a hardening spreadsheet, this is the week to check whether the October 2026 cumulative update is actually running on every DC, not only sitting in WSUS. If you hunt NTLM relay, the old heuristic “signing is on, so SMB-to-LDAP is dead” is the thing this bug was built to survive.

Introduction

LDAP signing sat on every NTLM-relay briefing as one of the controls that still worked. Patel’s team treated that as a claim to test. The bypass they landed on does not strip the SIGN flag and does not steal the victim’s session key. It opens a signed LDAP connection first, with a key the attacker already knows, and then re-authenticates that same connection as the victim.

Identity X is the session the attacker built: a domain user, or, where the DC allows it, an anonymous NTLM SASL context. Identity Y is the coerced victim. Y is a Windows SMB client, because the SMB client sets NTLMSSP_NEGOTIATE_SIGN in its NTLM messages. That flag used to be the reason SMB-to-LDAP relay failed. Here it is the reason the stacked operation is accepted. A WebDAV client does not set the flag, and the DC answers the stacked bind with operationsError (0x4DC) because a successful bind has not finished.

The operational consequence is the part that should move a patch window. Machine-account NTLM from a domain controller, arriving over SMB, can be relayed to LDAP on another DC from an unauthenticated or low-privilege starting point. LDAP write access on a computer object is enough for resource-based constrained delegation (msDS-AllowedToActOnBehalfOfOtherIdentity) or shadow credentials (msDS-KeyCredentialLink). From there the usual Kerberos primitives produce a TGT, DCSync follows, and the forest is gone. Patel is explicit that this post does not retell NTLM relay from scratch. The two references he points at are Elad Shamir’s Renaissance of NTLM Relay Attacks and the Hackndo NTLM Relay notes.

Note to readers

SpecterOps is in OpenAI’s Daybreak Trusted Access for Cyber Program. Patel writes that they used OpenAI’s Daybreak Red model under guidance while reversing the DC LDAP DLLs, and that the model helped pin the exact bug. They opened an MSRC report on 6 October 2026 and never got a reply. The fix shipped in the 13 October 2026 update. He does not claim the report caused the patch.

They found a cousin of this bug in SMB on their own. Microsoft patched SMB in September 2026 without a public write-up. Lydéric Lefebvre later documented that silent fix in Defeating Enforced SMB Signing. The LDAP variant in this post is the one Patel’s team found in September 2026 and is describing now.

Background: why signing was supposed to stop NTLM relay

Classic NTLM relay is a three-message cut-and-paste. NEGOTIATE from the client, CHALLENGE from the server you care about, AUTHENTICATE from the client, delivered to that server. NTLMv2 tightened the crypto and added per-message signing and sealing. Extended Protection for Authentication later added channel binding and service binding so a handshake captured on one TLS channel could not be replayed on another.

Cross-protocol relay is where the story got messy. Windows Server 2025 made SMB signing required by default for outbound connections, and new Active Directory installs require LDAP signing by default. Relaying SMB to LDAP stayed hard because the two stacks negotiate signing differently. SMB has its own signing negotiation and still sets the signing flag inside NTLM. LDAP has no separate negotiation; it reads the NTLM flags. When an SMB AUTHENTICATE is replayed onto LDAP, the DC honours NTLMSSP_NEGOTIATE_SIGN and expects every post-bind message to be signed.

That is the ordinary block: you do not know the victim’s exported session key, so you cannot sign. LDAPS with channel binding required is a second block, because the NTLM blob has to match the TLS channel the DC is looking at. HTTP is the easy source in the other direction. HTTP does not do per-message NTLM signing and does not even negotiate a session key; HTTPS leaves integrity to TLS. Coercing through WebClient therefore produces an NTLM exchange that is easy to relay and never asks for signing. WebDAV also lets an operator catch that authentication on an external host and route it back in. Elad Shamir named that path “There and Back Again.” Logan Goins wrote the operator notes in this SpecterOps post.

Until this bug, the combination people treated as closed was: LDAP signing required, channel binding Always, no WebDAV to hide in. Patel’s chain walks through the front door on 389/TCP with both of those controls on.

NTLMv2 basics: session keys, signing, and the MIC

The rest of the post uses a small set of NTLMv2 names. During a normal authentication the client takes its NT hash, the uppercased username and the domain name, and runs NTOWFv2 to get ResponseKeyNT. It then builds an NTLMv2_CLIENT_CHALLENGE: timestamp, client nonce, AV pairs. NTProofStr is HMAC-MD5 of the server challenge plus that structure, keyed with ResponseKeyNT. The NTLMv2 response on the wire is NTProofStr concatenated with the client challenge. Change an AV pair and the proof dies.

Diagram of NTLMv2 session keys, signing, and the MIC
NTLMv2 derivation: ResponseKeyNT, NTProofStr, SessionBaseKey, ExportedSessionKey, and the MIC over all three NTLM messages. Source: original article (image_6ffd0e.png).

The session key path is a second HMAC. SessionBaseKey is HMAC-MD5 of NTProofStr with ResponseKeyNT. For NTLMv2, KeyExchangeKey is the same value; Patel just calls it SessionBaseKey. When key exchange is on for signing or sealing, the client picks a random 16-byte ExportedSessionKey, RC4-encrypts it under SessionBaseKey, and puts the ciphertext in the AUTHENTICATE message. LDAP and SMB both start from that exported key and then derive their own signing and sealing keys with different KDF steps. Knowing one protocol’s signing key does not give you the other.

The Message Integrity Code sits on top of the three-message exchange. If MsvAvFlags in the AUTHENTICATE says a MIC is present, the client computed it over NEGOTIATE + CHALLENGE + AUTHENTICATE using the ExportedSessionKey. Edit any of those three messages and the server rejects the bind. That is why a relay that wants to flip flags in the AUTHENTICATE usually dies on the MIC. Patel’s technique never edits Y’s three messages. It wraps them, intact, inside a SASL record that X can sign.

ResponseKeyNT      = NTOWFv2(NT hash, USER, DOMAIN)
NTProofStr         = HMAC-MD5(ResponseKeyNT, ServerChallenge || CLIENT_CHALLENGE)
SessionBaseKey     = HMAC-MD5(ResponseKeyNT, NTProofStr)
ExportedSessionKey = random 16 bytes, RC4'd under SessionBaseKey
MIC                = keyed over NEGOTIATE || CHALLENGE || AUTHENTICATE
LDAP/SMB sign keys = KDF(ExportedSessionKey)   # different KDF per protocol

How LDAP signing and channel binding stop NTLM relay

If a DC requires LDAP signing, a relay that does not know the victim’s session key cannot talk after the bind. Signing is negotiated during the SASL bind and then covers LDAP messages, not only later Bind requests. No key, no valid signature, no modify.

SMB-to-LDAP makes that stricter. SMB always negotiates signing, so a coerced Windows SMB client sends NTLMSSP_NEGOTIATE_SIGN = 1. LDAP reads the flag even when the DC itself does not require LDAP signing, and then demands signatures for the rest of the connection. That is why the old playbooks said “do not relay SMB to LDAP; coerce WebDAV instead.” The statement holds for ordinary NTLMv2. Legacy NTLMv1 and broken implementations are the usual exceptions.

Channel binding is a different control and only exists where there is TLS. On LDAPS, NTLM can carry a channel-binding token that ties the handshake to that TLS session. LDAP on 389/TCP has no TLS channel, so there is nothing to bind. The pairwise bypass that operators have used for years is still true in the abstract:

  • Signing on, channel binding off: speak LDAPS. TLS already signs the transport, which satisfies the DC’s signing check, and there is no channel-binding token to fail.
  • Channel binding Always, signing off: speak LDAP on 389/TCP. There is no TLS channel to bind. After the bind you can still StartTLS, because TLS starts after authentication and channel binding was never checked.
  • StartTLS after a plaintext bind is useful when the relayed account is allowed to change passwords; those operations often want confidentiality.

With signing required and channel binding Always, that pairwise dodge is supposed to be gone. Patel’s test DC had both enforced. The attack still worked on 389/TCP. It does not transfer to LDAPS on 636/TCP, and if 389/TCP is blocked the technique as written dies.

How the LDAP signing bypass works

The bypass is re-authentication on a connection the attacker already owns. First, open a signed LDAP session whose session key you know. Valid domain credentials work. An anonymous NTLM SASL context works where the DC accepts it. Call that identity X. Then coerce a victim over SMB and relay Y’s NTLM onto that same socket.

The victim has to be an SMB client. WebDAV looks tempting because it is the usual NTLM-relay source, but a WebDAV client does not set the signing flag. When Patel stacked a bind plus a modify against a WebDAV-sourced AUTHENTICATE, the DC returned operationsError (0x4DC): a successful bind had not completed. The SIGN flag that used to kill SMB-to-LDAP is the flag this technique needs.

SASL is the wrapper that makes the rest possible. RFC 4422 is a plug-in authentication layer for protocols such as LDAP. NTLM or Kerberos authenticates; an optional security layer then signs and optionally encrypts every subsequent message. On the wire that layer is a length-prefixed SASL record: one signature (and optional seal) covering a blob that can hold more than one LDAP message. NTLM SASL signing on this connection uses X’s key. X knows that key. One signature covers everything inside the record.

The relay of Y’s AUTHENTICATE is therefore not a lone BindRequest. Patel puts two LDAP messages in one SASL record: the BindRequest that carries Y’s AUTHENTICATE, and a ModifyRequest that writes msDS-AllowedToActOnBehalfOfOtherIdentity or msDS-KeyCredentialLink. He signs the whole record with X. The DC verifies the record once under X, processes the bind, switches authorization to Y, and then executes the modify with Y’s privileges.

This does not give the attacker Y’s session key. A later SASL record signed with X is rejected, because the connection now wants Y’s key. The damage is inside the record that already passed. Contents were checked under X; the modify ran under Y.

Channel binding is untouched. It only applies to LDAPS. Patel’s DC had signing and channel binding both on; 389/TCP still worked. LDAPS fails for a structural reason, not a configuration one. On 636/TCP, TLS owns integrity. NTLM only authenticates inside that tunnel. The TLS keys belong to the connection, not to X, and Windows will not run NTLM SASL signing or sealing over an existing TLS session. There is no X-owned signing key and no signed SASL record in which to stack Y’s bind and the modify. That is why a 389/TCP signing bypass does not become an LDAPS signing bypass by changing the port.

LDAP signing bypass attack sequence via SMB coercion and a stacked SASL record
Attack sequence: coerce over SMB, open a signed LDAP session as X, relay Y’s NTLM, stack BindRequest plus ModifyRequest in one SASL record signed with X. Source: original article (image.jpeg).

Patel’s numbered sequence, kept in his order:

  1. The attacker coerces the victim into authenticating over SMB.
  2. The victim’s SMB client connects to the attacker and begins session setup.
  3. The attacker establishes a signed LDAP session through GSS-SPNEGO using either valid domain credentials or, where accepted, anonymous NTLM SASL authentication, with a session key known to the attacker.
  4. The victim’s SMB client sends an NTLM NEGOTIATE message.
  5. The attacker carries that message in a GSS-SPNEGO SASL BindRequest over the existing protected LDAP connection.
  6. The target returns an NTLM CHALLENGE message through GSS-SPNEGO, protected by the existing LDAP session.
  7. The attacker forwards the NTLM CHALLENGE message to the victim’s SMB client.
  8. The victim’s SMB client returns the final SPNEGO response carrying the NTLM AUTHENTICATE message.
  9. The attacker sends a GSS-SPNEGO BindRequest carrying that response, followed by a separate LDAP ModifyRequest, together in one SASL record signed using the attacker’s original LDAP session key.
  10. The target verifies the SASL record using the attacker’s original session key, completes the bind as the victim, and processes the following modification using the victim’s privileges.

Walkthrough: relaying SMB to LDAP with signing required

The packet capture is three hosts. The DC is LYS-DC at 10.4.10.47. The attacker is Kali at 10.4.10.233. The victim is LYS-SRV22 at 10.4.10.48. The DC was on build 26100.33451, September 2026 cumulative KB5129235, before October 2026 KB5126646 moved it to 26100.33648.

Coercion comes first. The victim opens SMB and sends NEGOTIATE, agreeing a dialect and listing security mechanisms. The relay does not answer yet. TCP ACKs the segment; the SMB session sits there until LDAP is ready. Holding the victim at NEGOTIATE is deliberate. X’s signed LDAP session has to exist before Y’s NTLM is allowed to move, because Y’s NEGOTIATE will travel as a SASL-protected BindRequest on that session.

Victim SMB NEGOTIATE held by the relay until LDAP is ready
Victim SMB NEGOTIATE; the relay ACKs at TCP and holds the session until the attacker LDAP bind exists. Source: original article (image_32ebc6.png).

Frame 12 is the attacker bind to the DC as LYS\LYS.LOW. The session key from that authentication is kept. Every later LDAP message on this socket has to be signed with it, and because the bind was LYS.LOW, the attacker has the key.

LDAP bind as LYS.LOW establishing the attacker session key
Frame 12: SASL bind as LYS\LYS.LOW. The session key from this bind signs every later record on the socket. Source: original article (image_9cc8e9.png).

The session key visible in the capture is the encrypted copy on the wire. It is not something you paste into a signer. The attacker already has the plaintext because they performed the bind.

Frame 28 resumes SMB. LYS\LYS-SRV22$ sends NTLM NEGOTIATE. As with any Windows SMB client, NTLMSSP_NEGOTIATE_SIGN is set.

NTLM NEGOTIATE from the Windows SMB client with SIGN set
Frame 28: victim computer account NTLM NEGOTIATE, NTLMSSP_NEGOTIATE_SIGN set. Source: original article (image_47ecd5.png).

Frame 29 relays that NEGOTIATE to the DC inside a BindRequest. The connection is already signed, so the bind is a SASL-protected message signed and sealed with LYS.LOW’s key. On the wire the payload looks encrypted. Patel wrote a small Wireshark dissector that decrypts these records with the LYS.LOW key so the later screenshots are readable.

Relayed NTLM NEGOTIATE inside a SASL-protected LDAP BindRequest
Frame 29: NEGOTIATE carried in a BindRequest, SASL-signed and sealed with LYS.LOW’s key. Source: original article (image_464200.png).

Frames 30 to 33 finish NTLM. The DC sends CHALLENGE, the relay forwards it to Y over SMB, Y returns AUTHENTICATE. The relay is a copier. It never learns Y’s password or session key.

NTLM CHALLENGE and AUTHENTICATE copied between SMB and LDAP
Frames 30–33: CHALLENGE from the DC, AUTHENTICATE from LYS-SRV22$, copied without modification. Source: original article (image_d0d5d2.png).

Frame 34 is the stacked record. Two LDAP messages, one SASL wrapper, signed with X. First: BindRequest carrying Y’s AUTHENTICATE. Second: ModifyRequest writing msDS-AllowedToActOnBehalfOfOtherIdentity on LYS-SRV22$, with a security descriptor that grants an account the attacker controls, HXRBOX2$. The DC verifies the SASL signature once with X, completes the bind as Y, and processes the modify as Y. The whole RBCD write happens inside that one signed record.

Stacked BindRequest and ModifyRequest in one SASL record
Frame 34: BindRequest (Y’s AUTHENTICATE) plus ModifyRequest in one SASL record signed with X. Source: original article (image_40178f.png).
Decrypted ModifyRequest writing the RBCD descriptor
Decrypted view of the stacked ModifyRequest: msDS-AllowedToActOnBehalfOfOtherIdentity on LYS-SRV22$ granting HXRBOX2$. Source: original article (image_bc94b4.png).

Root cause: inside LDAP_CONN::ProcessRequestEx

On the DC the interesting function is LDAP_CONN::ProcessRequestEx. It runs for every LDAP request on a connection. If signing was negotiated, it expects the message to be signed with the current security context’s key, and it asks LDAP_REQUEST::DecryptSignedOrSealedMessage to check the SASL record. Signing only starts after NTLM has finished. The attacker finished NTLM as X, so they hold that key when re-authentication begins. Once Y’s AUTHENTICATE is in hand, it is relayed in a BindRequest together with the attacker’s LDAP operation, both in the same SASL record, signed with X.

LDAP_CONN::ProcessRequestEx signing check before processing the request
ProcessRequestEx: if signing is on, DecryptSignedOrSealedMessage checks the SASL record against the current context. Source: original article (image_891220.png).

Next, ProcessRequestEx calls LDAP_CONN::BindRequest, which validates Y. Inside that path, LDAP_CONN::MoveSecurityContext, LDAP_CONN::InstallNewSecurityContext and LDAP_CONN::SetSecurityContextAtts switch the connection’s authorization to the victim.

BindRequest path that moves the connection to the victim identity
BindRequest validates Y and then MoveSecurityContext / InstallNewSecurityContext switch authorization. Source: original article (image_e8290d.png).
InstallNewSecurityContext and SetSecurityContextAtts
InstallNewSecurityContext and SetSecurityContextAtts install Y as the connection’s security context. Source: original article (image_0b8415.png).

When BindRequest returns, unread LDAP still sits in the same SASL record. ProcessRequestEx takes the next message and calls LDAP_CONN::ModifyRequest. Authorization is already Y, so the modify is Y. There is no check that the identity changed halfway through the record, and nothing that re-verifies the remainder with Y’s signing key — the key only Y holds. The SASL record is verified once, with a key the attacker controls, and never again.

ModifyRequest processed after the identity switch inside the same SASL record
After BindRequest returns, leftover SASL payload is parsed as ModifyRequest and runs as Y. Source: original article (image_2a8045.png).
LDAP_CONN::ProcessRequestEx
    LDAP_REQUEST::DecryptSignedOrSealedMessage   # once, key of current context (X)
    LDAP_CONN::BindRequest
        LDAP_CONN::MoveSecurityContext
        LDAP_CONN::InstallNewSecurityContext
        LDAP_CONN::SetSecurityContextAtts        # connection is now Y
    LDAP_CONN::ModifyRequest                     # still the same SASL record, now as Y

How Microsoft patched the LDAP signing bypass

The October 2026 fix gives every LDAP connection an identity counter. When new client data arrives, LDAP_REQUEST::ReceivedClientData reads the connection’s current counter and copies it onto the request. That copy is a snapshot of who the connection was when this blob showed up. Nothing is blocked here; the value is only stored.

ReceivedClientData snapshots the connection identity counter onto the request
ReceivedClientData copies the connection identity counter onto the request when client data arrives. Source: original article (image_cb31bf.png).

The counter increments only when identity actually changes. The relayed Bind is exactly that change. When the bind completes, SetSecurityContextAtts installs Y and bumps the counter, leaving a trace that a switch happened.

SetSecurityContextAtts increments the identity counter on a successful bind
SetSecurityContextAtts installs the victim context and increments the connection identity counter. Source: original article (image_b3bb47.png).

Later, still inside the same request, ProcessRequestEx compares the connection counter with the copy saved on the request. They no longer match: the bind moved the connection to Y, the snapshot still says X. The function throws away the rest of the SASL record. The stacked Modify never runs.

ProcessRequestEx drops the remainder of the SASL record after an identity change
Counter mismatch: identity changed inside the record, remainder discarded, stacked Modify never executes. Source: original article (image_ddc280.png).
on client data:
    request.identity_snapshot = connection.identity_counter

on successful Bind:
    install Y
    connection.identity_counter += 1

on next LDAP message in the same SASL record:
    if connection.identity_counter != request.identity_snapshot:
        drop remainder          # stacked Modify never runs
    else:
        process as usual

The patch is a same-record rule, not a ban on re-bind. A later, separate SASL record that is a BindRequest on its own can still change identity the way LDAP always allowed. What it cannot do is smuggle a write in the bytes that were signed under the previous key. That is the entire fix.

Proof of concept: ntlmrelayx and LDAP Switcheroo

Patel released the lab wrapper as “The LDAP Switcheroo,” a thin layer over Impacket’s ntlmrelayx.py with the SASL stacking changes. The tree lives at github.com/grayhatkiller/switcheroo. This post does not re-host that tree. The usage block below is the public README, placeholders left in place.

The README is blunt about prerequisites. Python 3, root on the listener host because SMB wants TCP/445. A pinned Impacket that actually exposes LDAP signing and security-context APIs; the PyPI 0.13.1 package is not enough. PYTHONPATH has to point at that tree, and -ntlmrelayx has to point at its ntlmrelayx.py. A coercion tool (Coercer, PetitPotam, or equivalent) has to drive the victim at the listener. The victim must authenticate over SMB. HTTP/WebDAV is not a source for this path.

sudo PYTHONPATH=/path/to/pinned-impacket python3 switcheroo.py \
    'DOMAIN/attacker[:password]@DC.fqdn' \
    -dc-ip <DC_IP> \
    -base-dn 'DC=example,DC=com' \
    -listen-ip <ATTACKER_IP> \
    -victim 'DOMAIN/VICTIM$' \
    -modify-dn 'CN=VICTIM,CN=Computers,DC=example,DC=com' \
    -rbcd-sid '<ATTACKER_COMPUTER_SID>' \
    -ntlmrelayx /path/to/pinned-impacket/examples/ntlmrelayx.py

Then coerce the victim toward -listen-ip from another terminal. Credentials follow Impacket’s [domain/]username[:password]@host. Omit the password to be prompted, or pass -hashes LMHASH:NTHASH / -no-pass. Keep stdin open; ntlmrelayx exits on EOF:

tail -f /dev/null | sudo PYTHONPATH=... python3 switcheroo.py ... &
OptionMeaning
target[domain/]username[:password]@<DC host/IP> — attacker account + target DC
-dc-ip / -base-dn / -listen-ipDC IP, search base, attacker listener IP
-victim DOMAIN/accountonly relay this victim
-modify-dnDN of the object to modify as the victim
-rbcd-sid SIDwrite an RBCD descriptor granting this SID
-value / -value-hex / -attributewrite an arbitrary attribute instead of RBCD
-ntlmrelayx PATHpath to ntlmrelayx.py
-hashes, -no-pass, -debug, -tsauth / logging
LDAP Switcheroo key options, from the public README. Source: github.com/grayhatkiller/switcheroo.

The tool prints the DC’s Bind and Modify result codes and then does an independent signed read-back to confirm the write. The SMB/coercion side may still print FAILED. That line is about the SMB session the victim opened, not about whether the LDAP modify committed. If you are validating a lab, trust the LDAP result codes and the read-back, not the SMB error.

How to defend against NTLM relay to LDAP

Signing alone was never a complete defence against NTLM relay. Patel’s list after this bug is short and ordered on purpose:

  • Patch domain controllers first. October 2026 cumulative update KB5126646 on every DC. That is the LDAP signing bypass.
  • Patch for the SMB fix. September 2026 KB5122882 on every server that still speaks SMB with signing required.
  • Enforce LDAP signing and channel binding together. Either one alone leaves a path. With both on, an attacker cannot flip to LDAPS or plaintext LDAP to dodge the missing control.
  • Restrict LDAP on 389/TCP where you can. This technique ran on 389/TCP. If that port is blocked, the write-up as published does not work against 636/TCP.
  • Reduce NTLM wherever possible. Every NTLM authentication you remove is one less handshake an attacker can relay.

The last two are the expensive ones. Legacy applications still open plaintext LDAP and still fall back to NTLM. Treat them as a programme, not as a GPO you flip on Friday. The patch on the DCs is the control that closes this specific stacking bug even if 389/TCP has to stay open.

Acknowledgements, prior work, and references

Frequently asked questions

What is the LDAP signing bypass?

An NTLM relay that still worked when LDAP signing was required. The attacker opens a signed LDAP connection with a session key they know, relays a victim’s NTLM over that connection, and places a malicious LDAP operation in the same signed SASL record. The DC checks the signature with the attacker’s key, then runs the operation with the victim’s privileges.

Is LDAP signing enough to stop NTLM relay?

Not on its own. Before the October 2026 update, every Windows Server 2022 and Windows Server 2025 DC Patel tested accepted relayed NTLM with LDAP signing required. Install the update, enforce signing and channel binding together, and cut NTLM where you can.

Does it work against LDAPS?

The technique targets LDAP on 389/TCP. If 389/TCP is blocked, it does not work against LDAPS on 636/TCP. TLS on 636/TCP owns integrity; there is no NTLM SASL signing key belonging to the attacker’s first identity in which to stack a second bind and a modify.

Which Windows Server versions were vulnerable?

Patel tested from Server 2016 domain controllers upward. They were vulnerable until the October 2026 cumulative update.

Does the attacker need domain credentials?

Not necessarily. They need a signed LDAP session with a session key they know. That can be a domain account, or, where the DC allows it, an anonymous LDAP SASL bind. They also need to coerce a victim into authenticating to them over SMB.

What can an attacker do with it?

Make a DC authenticate to the attacker and relay that to another DC. In the worked example they relayed a server computer account and wrote msDS-AllowedToActOnBehalfOfOtherIdentity for RBCD. Shadow credentials on msDS-KeyCredentialLink is the sibling write. From a DC machine account the same pattern is domain-wide.

Has Microsoft patched it?

Yes. Reported to Microsoft on 6 October 2026. A fix shipped on 13 October 2026 as KB5126646, independently of any public statement about the MSRC case. The patch tracks identity with a counter and discards the rest of a SASL record if identity changes inside it. The related SMB issue was fixed in September 2026.

CWE, ATT&CK, and the two KBs

The LDAP bug is incomplete verification of a cryptographic record after an in-stream identity change. CWE-345 (Insufficient Verification of Data Authenticity) fits the SASL record that is checked once and then trusted after the principal changes. CWE-287 (Improper Authentication) covers the authorization switch that follows a bind whose surrounding bytes were signed by someone else. On the ATT&CK side the coercion plus relay is T1557.001 (Adversary-in-the-Middle: LLMNR/NBT-NS Poisoning and SMB Relay) in spirit even when the coerce is PetitPotam or a printer bug rather than name poisoning; T1187 (Forced Authentication) is the coerce itself; T1558.005 (Shadow Credentials) and the RBCD family under T1134.005 sit on the LDAP write.

IDWhat it names here
CWE-345SASL record authenticated under X, contents executed after bind to Y
CWE-287Authorization context replaced mid-record without a fresh MAC under Y
ATT&CK T1557.001NTLM relay of a coerced SMB client onto LDAP
ATT&CK T1187Forced authentication (PetitPotam / Coercer / equivalent)
ATT&CK T1134.005 / T1558.005RBCD or shadow credentials as the LDAP write
KB512664613 Oct 2026 — LDAP identity counter, DC build 26100.33648 in Patel’s lab
KB5122882Sep 2026 — SMB signing re-authentication cousin
KB5129235Sep 2026 CU still on the vulnerable lab DC (build 26100.33451)
Editorial mapping. CVE IDs were not published in the SpecterOps post.

No CVE number appears in Patel’s article. Hunt and patch on the KB and the build, not on a CVE string that is not there.

LDAP, LDAPS, and SMB, on one page

The reason this bug is easy to mis-remediate is that three different mechanisms all get called “signing.” They do not share a key, a layer, or a failure mode.

ChannelWho signsNTLM SIGN flagStacking Bind+Modify under XChannel binding
LDAP 389/TCP, signing requiredNTLM SASL, per session keyHonoured; post-bind MAC requiredWorked until KB5126646N/A (no TLS)
LDAPS 636/TCPTLS, connection keysNTLM authenticates inside TLS; no NTLM SASL sign/seal over TLSDoes not transferEPA / CBT when configured Always
SMB (Windows client)SMB session signing, own KDFAlways set by the Windows SMB clientCousin bug, quiet Sep 2026 patchN/A for this write-up
HTTP / WebDAVNone at NTLM layerNot setDC returns operationsError 0x4DC on the stacked recordOnly if HTTPS + EPA
Editorial comparison from Patel’s measurements. Signing on LDAP 389/TCP is the row this bug lives in.

A hardening ticket that says “enable LDAP signing” and stops there still leaves 389/TCP open to this stacking trick on unpatched DCs, and still leaves LDAPS open to classic relay if channel binding is not Always. A ticket that says “block 389/TCP, LDAPS only, channel binding Always, October CU on every DC” is the one that matches the research.

What a hunt looks like this week

You are looking for three things that should not sit on the same LDAP connection in a short window: a bind as a low-privilege or anonymous context, a second bind as a computer account, and a modify of msDS-AllowedToActOnBehalfOfOtherIdentity or msDS-KeyCredentialLink before the connection closes. Windows does not log SASL record boundaries. Directory Service logging and ETW get you the binds and the writes, not the fact that they shared a signature.

# leftover hunt notes, not an exploit
# 1) DC patch level: KB5126646 / build >= 26100.33648 (lab number from Patel)
#    Get-HotFix -Id KB5126646
#    winver / [System.Environment]::OSVersion on the DC itself
# 2) LDAP 389/TCP still reachable from workstation VLANs?
# 3) Event 2889 / 3039 (unsigned LDAP) are the OLD signing hunt.
#    This bug speaks signed LDAP. Those events will be quiet.
# 4) Directory Service / 5136 on computer objects:
#    msDS-AllowedToActOnBehalfOfOtherIdentity
#    msDS-KeyCredentialLink
#    correlate with 4624 type 3 from a non-DC toward the DC LDAP port
#    and a 4648 / coerce burst (PrinterBug, PetitPotam, EfsRpc) a few seconds earlier
# 5) two successive binds on one LDAP TCP 5-tuple, first user or anonymous, second machine$
# 6) ntlmrelayx / switcheroo operator noise: SMB 445 listener, failed SMB session after a successful LDAP modify

Event 2889 is the trap. It fires when a client talks unsigned LDAP. Patel’s client talks signed LDAP. A hunt built only on 2889 will file this under “signing is working.” Shift the hunt to attribute writes on computer objects plus dual binds on one TCP session, and to coerce patterns that target SMB rather than WebDAV.

If you own a lab, the honest pre-patch check is the one Patel ran: signing required, channel binding Always, coerce a computer account over SMB, stack the bind and the RBCD write. After KB5126646 the bind may still succeed and the modify in the same record must not. That is the only functional test that matches the patch. Do it on systems you are allowed to break.

Key Takeaways

  • LDAP signing required did not stop NTLM relay if the attacker already owned a signed session and stacked a victim bind with a write in one SASL record.
  • The Windows SMB client’s SIGN flag is required for this path. WebDAV coercion does not carry it and the stacked record fails with operationsError 0x4DC.
  • The DC verified the SASL MAC once under identity X, switched to Y inside BindRequest, and ran ModifyRequest as Y with no second MAC.
  • LDAPS on 636/TCP is a different layer. TLS keys are not X’s NTLM SASL keys. Blocking 389/TCP kills this write-up as published.
  • Patch is KB5126646 (13 Oct 2026), an identity counter that drops the remainder of a SASL record when identity changes mid-record. SMB cousin: KB5122882 in September.
  • Impact is RBCD or shadow credentials on the coerced computer, including DC-to-DC, then DCSync. Anonymous or low-priv LDAP is enough to start if the DC accepts that first bind.
  • Hunts that watch for unsigned LDAP will miss this. Watch dual binds on one TCP session and writes to msDS-AllowedToActOnBehalfOfOtherIdentity / msDS-KeyCredentialLink.

Defensive Recommendations

  1. Install KB5126646 on every domain controller and confirm the running build, not only the package in the catalog. Patel’s post-patch lab build was 26100.33648.
  2. Install KB5122882 (September 2026) on SMB servers so the quiet SMB re-authentication fix is actually present.
  3. Set LDAP signing to required and LDAP channel binding to Always. One without the other is the pairwise dodge from the last five years of relay notes.
  4. Filter 389/TCP at the DC so that only the hosts that still need plaintext LDAP can reach it. Prefer LDAPS 636/TCP for everything else.
  5. Audit computer-object writes of msDS-AllowedToActOnBehalfOfOtherIdentity and msDS-KeyCredentialLink; alert on a bind-as-user plus bind-as-machine$ on one LDAP 5-tuple.
  6. Turn off NTLM where an application can live on Kerberos. Every remaining NTLM client is a coerce target.
  7. Disable or constrain the usual coerce endpoints (Print Spooler, EFS RPC, PetitPotam-class interfaces) on DCs and on servers whose machine accounts are worth relaying.
  8. Treat Switcheroo / a pinned Impacket ntlmrelayx as lab tooling. Do not drop a filled relay client into a shared repo; point at the public GitHub tree.

Conclusion

LDAP signing did what it said on the tin: it demanded a MAC from the session that already existed. It did not demand that the MAC still belong to the principal that would execute the next message in the same SASL record. Raj Patel, with Duane Michael, showed that gap on every DC they tried from Server 2016 through Server 2025, with signing on, with channel binding on, over 389/TCP, from an SMB coerce. Microsoft closed it in KB5126646 by snapshotting identity when bytes arrived and throwing away the rest of the record when identity moved. Until that binary is the one listening on your DCs, a signed LDAP session you already own is a place you can park someone else’s AUTHENTICATE and a write, and the DC will thank you for the valid signature.

Original text: “Signed, Sealed, and Relayed, NTLM-style: Bypassing LDAP Signing Through Reauthentication” by Raj Patel at SpecterOps, 23 October 2026.

oxfemale Vulnerability research, reverse engineering, and exploit development.
// Discussion