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

Author

Raj Patel

Read Time

21 mins

Published

Oct 23, 2026

Share

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. Microsoft fixed SMB quietly in September 2026 and LDAP in October 2026. Every Windows Server from 2016 to 2025 was vulnerable to NTLM relay attacks, even with signing enforced in both protocols.

Introduction

Traditionally, LDAP signing was considered one of the “effective mitigations” against NTLM relay attacks. So, naturally, we had to ruin it. To circumvent LDAP signing, we first establish a signed LDAP connection to the DC using valid credentials or an anonymous security context and keep the session key. We then coerce a victim and relay their NTLM authentication over that existing LDAP connection, slipping our malicious LDAP operation into the same signed SASL record as the victim’s NTLM AUTHENTICATE message. The DC verifies the signature using our session key, switches authorization to the victim’s identity, and executes the malicious LDAP operation with the victim’s privileges. Microsoft patched this in October of 2026, but every Windows Server was vulnerable until then.

You’re probably thinking: “So what, Raj? Why should I care?” This bypass enables machine account NTLM authentication from a domain controller over SMB to be relayed to LDAP on another domain controller, from an unauthenticated or unprivileged context.  Relay to LDAP enables the total compromise of the victim domain controller via computer takeover primitives such as Kerberos resource-based constrained delegation (RBCD)  and shadow credentials, which then leads to DCSync and complete domain and forest compromise.

To better understand NTLM and relay attacks in detail, please read the following blogs, as this blog post will not cover the basics:

Note to readers

SpecterOps partners with OpenAI as part of the Daybreak Trusted Access for Cyber Program. We used OpenAI’s Daybreak Red model to assist in this research. Specifically, the model excelled at reverse engineering the DLLs under guidance and pinpointing the exact bug, which significantly reduced the research time. We reported this vulnerability to Microsoft on October 6, 2026 but we never received a response to that MSRC report. The fix shipped on October 13, 2026 update, so we can’t say whether our report had anything to do with it.

We independently identified a similar vulnerability in SMB, and Microsoft patched it quietly in September 2026. Another researcher also noticed that silent patch and published a blog, Defeating Enforced SMB Signing. This blog post describes an LDAP variant that we found in September 2026.

Background: Why Signing Was Supposed to Stop NTLM Relay

NTLM relay attacks used to be straightforward: an attacker positioned between a client and server could relay the NTLM authentication exchange and authenticate to the server as the client. 

Microsoft introduced NTLMv2 to strengthen NTLMv1’s authentication and session security. NTLMv2 supports signing to protect message integrity and sealing to encrypt messages. Later, Extended Protection for Authentication (EPA) added channel and service binding to help prevent relay attacks. 

Since then NTLM relay attacks have become more complicated, especially cross-protocol relaying. Windows Server 2025 raises the baseline further. SMB signing is now required by default for outbound connections, and new Active Directory deployments require LDAP signing by default. Relaying from SMB to LDAP is still harder because the two protocols handle signing differently. SMB negotiates signing on its own, but the Windows SMB client still sets the signing flag in its NTLM messages. LDAP has no separate negotiation, so it relies on NTLM to decide whether signing is used. When we relay that SMB authentication to LDAP, the DC honors the flag and expects every message after the bind to be signed.

The ordinary relay was blocked when SMB or LDAP required signing because we did not know the victim’s exported session key. LDAP over TLS was blocked when channel binding was required. This is where relaying from HTTP became easier because HTTP does not provide per-message signing and it doesn’t even negotiate a session key; when HTTPS is used, TLS provides transport integrity instead. As a result, coercing a victim through WebClient generally produces an NTLM exchange that is easier to relay and never negotiates signing. WebDAV also allows us to capture that authentication on an external host and route it back into the target environment through attacker-controlled infrastructure. Elad Shamir calls this technique “There and Back Again,” and Logan Goins demonstrates the complete workflow in this blog post.

NTLMv2 Basics: Session Keys, Signing, and the MIC

We will briefly cover NTLMv2 terms so we are all on the same page when these terms come up later. During normal NTLMv2 authentication, the client uses its NT hash, uppercased username and domain name to derive the ResponseKeyNT through the NTOWFv2 function. It then builds the NTLMv2_CLIENT_CHALLENGE structure, which contains a timestamp, a client challenge, and AV pairs. The client then computes the NTProofStr by running HMAC-MD5 over the server challenge and the NTLMv2_CLIENT_CHALLENGE structure using the ResponseKeyNT as the key. The final NTLMv2 response is the concatenation of the NTProofStr and the NTLMv2_CLIENT_CHALLENGE structure. So the AV pairs are protected by the NTProofStr, and changing them would invalidate that proof. 

For NTLMv2, the client computes a SessionBaseKey by running HMAC-MD5 over the NTProofStr using the ResponseKeyNT as the key. The KeyExchangeKey is the same as SessionBaseKey and we will refer to it as the SessionBaseKey in this blog post. When key exchange is negotiated for signing or sealing (encryption), the client generates a random 16-byte ExportedSessionKey and encrypts it with RC4 using the SessionBaseKey. That encrypted key is included in the AUTHENTICATE message. The ExportedSessionKey is used as input to derive signing and sealing keys by both LDAP and SMB protocols, but each protocol derives its own signing and sealing keys differently. 

Another important concept is the Message Integrity Code (MIC). The MIC protects the entire NTLM exchange from tampering. The MsvAvFlags AV pair in the AUTHENTICATE message tells the server that a MIC is present. The client computes the MIC over all three NTLM messages using the ExportedSessionKey, so any modification to any of those messages invalidates the MIC, resulting in the server rejecting authentication.

How LDAP signing and channel binding stop NTLM relay

Typically, if a target DC requires LDAP signing, we cannot perform NTLM relay against LDAP because we don’t know the victim’s (client’s) session key. The signing requirement is negotiated during the SASL bind and protects the LDAP messages sent after authentication, not just subsequent Bind requests. Without the session key, we cannot generate valid signatures for LDAP messages.

When relaying SMB to LDAP, things get more complicated because both protocols have their own way of negotiating signing. SMB always negotiates signing, and the NTLMSSP_NEGOTIATE_SIGN flag will always be set to 1 for a victim coerced via SMB. The problem arises when the LDAP server honors that request even though the DC does not require signing for LDAP. So we can’t relay SMB to LDAP because LDAP enforces the signing the victim requested. This applies to typical NTLMv2 exchanges, with exceptions such as legacy NTLMv1 or vulnerable implementations.

LDAP also supports channel binding when authentication happens over LDAPS (with TLS). Signing protects the LDAP messages, while channel binding ties the authentication to the TLS channel. Channel binding only applies to LDAPS, and it doesn’t apply to LDAP.

Traditionally, we could bypass LDAP signing and channel binding if only one protection is enforced. If LDAP signing is enforced but channel binding is not, we can target LDAPS, and since TLS traffic is signed, it satisfies the DC’s signing requirements. If channel binding is enforced but LDAP signing is not required, we can instead target LDAP, where there is no TLS channel to bind the authentication to. After authentication, we may upgrade that connection with StartTLS because TLS is established after the bind, and channel binding was not checked during that authentication exchange. The benefit of using StartTLS is that it allows performing sensitive operations such as password changes or resets, assuming the relayed account has the necessary permissions.

With LDAP signing required and channel binding set to Always, NTLM relay to LDAP should be impossible. However, as we’ll see, the word itself says “I’m possible”.

How the LDAP signing bypass works

Buckle up! Now the fun begins. We identified a way to bypass LDAP signing by coercing a Windows SMB client and relaying its NTLM authentication to a domain controller that requires LDAP signing. First, we establish a signed LDAP connection with a session key we know. We can do this with valid domain account credentials, or we can even establish an anonymous NTLM SASL context. We will call this identity X. We then relay the victim’s (identity Y) NTLM authentication over that existing signed connection.

You might be asking why the victim has to be an SMB client. If WebDAV can be coerced too, why not relay that? This is where the SMB client turns out to be the important part. The Windows SMB client sets the NTLM signing flag in its authentication, which is the exact thing that made SMB to LDAP relaying hard before. A WebDAV client doesn’t set that flag, and when the victim authenticates over HTTP, the DC rejects the stacked operation with operationsError (0x4DC) because a successful bind had not completed. So the signing flag that used to be the problem is what makes this technique work.

Before going further, we need to explain SASL. SASL (Simple Authentication and Security Layer) is a framework that lets a protocol like LDAP plug in an authentication mechanism, such as NTLM or Kerberos, and optionally add a layer that signs and encrypts every message after the bind. Once that layer is on, LDAP sends a SASL record, which is a length-prefixed block that is signed (and optionally sealed) as one unit and can hold more than one LDAP message. NTLM SASL signing creates a signing key that is associated with X, and we know that key. The important part is that one signature covers everything inside a SASL record.

The interesting part is what happens when we relay Y’s final NTLM AUTHENTICATE message. Instead of sending it on its own, we put the BindRequest carrying that message and our malicious LDAP ModifyRequest (e.g., modifying msDS-AllowedToActOnBehalfOfOtherIdentity or msDS-KeyCredentialLink attributes for Kerberos resource-based constrained delegation or shadow credentials, respectively) into one SASL record. These are two separate LDAP messages inside the same protected SASL record, and we sign the whole SASL record with the session key we already know (X). The DC verifies the entire SASL record once using X, then processes the BindRequest and switches the connection’s identity to Y. When it reaches our modification, it runs the LDAP operation with Y’s privileges, even though we supplied the LDAP operation and signed the SASL record with X.

This does not give us the victim’s session key (Y). If we send another SASL record signed with X, the DC rejects it because the connection now requires Y’s session key. The issue is what happens inside the SASL record we already sent. Its contents pass the signature check under our original security context (X), but the modification runs under Y’s identity after the bind completes.

This technique bypasses LDAP signing, not channel binding. Channel binding only applies to LDAPS, because it ties authentication to the TLS channel, and LDAP (389/TCP) has no TLS channel to bind to. The domain controller we tested against had both LDAP signing and channel binding enforced, and the attack still worked over 389/TCP. If 389/TCP is blocked then this technique will not work against LDAPS (636/TCP).

So why doesn’t the signing bypass itself carry over to LDAPS? The key difference between signed LDAP on 389/TCP and LDAPS on 636/TCP is where the protection is applied. LDAPS relies on TLS to sign and encrypt the connection, while NTLM only authenticates the user inside it. The TLS keys are associated with the connection and not with X, and Windows does not allow NTLM SASL signing or sealing over an existing TLS connection. So on LDAPS there is no signing key that belongs to X, and no signed SASL record to stack Y’s authentication and our operation into. This is why the LDAP signing bypass on 389/TCP does not transfer directly to LDAPS.

The attack sequence follows:

  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

Now let’s dive deeper into this vulnerability and analyze how it works and what made it possible.

In this example, we have three hosts. The domain controller is LYS-DC (10.4.10.47), the attacker is a Kali host (10.4.10.233), and the victim is LYS-SRV22 (10.4.10.48). The DC was running build 26100.33451 (KB5129235, the September 2026 cumulative update) before the October 2026 update (KB5126646, build 26100.33648) introduced the fix.

First, we coerce the victim into connecting to the attacker host. The victim opens an SMB connection and sends an SMB NEGOTIATE request, which agrees on the SMB dialect and lists the security mechanisms it supports. We don’t respond yet. The TCP layer acknowledges the packet, but the relay holds the victim at this point until our LDAP connection is ready.

In frame 12, we bind to the DC over LDAP as LYS\LYS.LOW and keep the session key from that authentication. Every LDAP message after the bind has to be signed with this key, and since we authenticated as LYS.LOW we know it.

The session key shown above is in encrypted format so we can’t directly use it.

In frame 28, we resume the victim’s SMB connection and the victim (LYS\LYS-SRV22$) sends us its NTLM NEGOTIATE message. As with any Windows SMB client, the NTLMSSP_NEGOTIATE_SIGN flag is set, as you can see in the screenshot below. 

In frame 29, we relay that NTLM NEGOTIATE message to the DC inside a Bind request. Because the connection is already signed, the Bind travels as a SASL-protected message signed and sealed with LYS.LOW’s session key. That is why the payload looks encrypted in the screenshot. We wrote a small Wireshark plugin that decrypts these messages with the LYS.LOW key so we can see what is being sent, which we will show later.

In frames 30 to 33, we finish the NTLM exchange. The DC sends a NTLM CHALLENGE message to us, we forward it to Y (LYS\LYS-SRV22$) over SMB, and Y answers with its NTLM AUTHENTICATE message. We only pass the messages between Y and the DC. We never learn Y’s password or session key.

Frame 34 is where the attack happens. Instead of sending Y’s AUTHENTICATE message on its own, we put two LDAP messages into one SASL record and sign it with X’s session key. The first is a BindRequest carrying Y’s AUTHENTICATE message. The second is a ModifyRequest that writes msDS-AllowedToActOnBehalfOfOtherIdentity on LYS-SRV22$, with a security descriptor that grants an account we control (HXRBOX2$). The DC verifies the signature on the SASL record once, using X’s key. It then completes the bind as Y and processes the ModifyRequest as Y too. So the whole resource-based constrained delegation (RBCD) attack happens in one signed SASL record.

Root Cause: Inside LDAP_CONN::ProcessRequestEx

To uncover what happens on the domain controller side, the core of the vulnerability lives inside the LDAP_CONN::ProcessRequestEx function. This function runs for every LDAP request received on a connection. It first checks whether signing was negotiated on the connection, and if it was, it expects the message to be signed with the negotiated signing key. It verifies this by calling LDAP_REQUEST::DecryptSignedOrSealedMessage, which checks the SASL record against the key of the connection’s current security context. One thing to note here is that signing only starts after the NTLM authentication exchange is completed. Since we were the ones who authenticated on this connection, we already possess the session key at this point. So when we perform the reauthentication, we continue the open SMB connection with the victim and relay the NTLM messages back and forth with the DC. Once we have the victim’s final NTLM AUTHENTICATE message, we relay it in a Bind request together with our own LDAP operation, both in the same SASL record signed with our key.

Next, ProcessRequestEx calls the LDAP_CONN::BindRequest function, where the victim’s identity is validated. Within that function, it later calls LDAP_CONN::MoveSecurityContext, LDAP_CONN::InstallNewSecurityContext, and LDAP_CONN::SetSecurityContextAtts,  which switch the connection’s authorization to the victim’s.

After LDAP_CONN::BindRequest returns, there is still data left in the SASL record, so ProcessRequestEx processes the next message and calls the LDAP_CONN::ModifyRequest function. This is where our stacked LDAP operation goes, the one we wanted to perform with the victim’s authorization. Since the authorization context was already switched to the victim during the Bind, the Modify executes as the victim. So basically there is no check that notices the identity changed partway through the SASL record, or that anything after the reauthentication should be verified with the new signing key, which only the victim holds. The SASL record gets verified just once, with the session key that we control, and never again after that.

How Microsoft Patched the LDAP Signing Bypass

Microsoft fixed this issue by giving every LDAP connection a counter that tracks its identity. So first it captures a baseline. Whenever new client data arrives, LDAP_REQUEST::ReceivedClientData reads the connection’s current identity counter and saves a copy of it inside the request itself. This is like a snapshot of which identity the connection was authenticated as when that data came in. Nothing is validated or blocked at this point – it’s just recorded.

That counter only increments when the connection’s identity actually changes, and that is exactly what our relayed Bind does. When the Bind completes, LDAP_CONN::SetSecurityContextAtts installs the victim’s security context and increments the same counter, which leaves a trace that the switch happened.

Later, after the Bind in that request has switched the connection over to the victim, the counter on the connection has increased, but the copy saved in the request hasn’t. So when ProcessRequestEx compares the two, and they don’t match, it knows the identity changed partway through the SASL record. It then throws away the rest of the SASL record, which means our stacked Modify never runs.

Proof of Concept: Running the Attack With ntlmrelayx

To test out the above described attack, we are releasing “The LDAP Switcheroo” that wraps ntlmrelayx.py with our custom modifications to make this attack work.

https://github.com/grayhatkiller/switcheroo  

How to Defend Against NTLM Relay to LDAP

Signing alone was never a complete defense against NTLM relay. To close the gap this research exposed:

  • Patch domain controllers first. Install the October 2026 cumulative update (KB5126646) on every domain controller. This fixes the LDAP signing bypass.
  • Patch for the SMB fix. Install the September 2026 update (KB5122882) on every server to close the SMB signing bypass.
  • Enforce LDAP signing and channel binding together. Either one on its own leaves a path open. With both enforced, an attacker can’t switch to LDAPS or plain LDAP to get around the one that’s missing.
  • Restrict LDAP on 389/TCP where you can. The bypass ran over 389/TCP. If 389/TCP is blocked then this technique fails.
  • Reduce NTLM wherever possible. Every NTLM authentication you remove is one less thing an attacker can relay.

The last two recommendations are more complicated and may be aspirational goals for some organizations, because legacy applications often depend on plain LDAP and NTLM.

Acknowledgements, Prior Work, and References

Frequently Asked Questions

What is the LDAP signing bypass? 

It is an NTLM relay technique that worked against domain controllers even when LDAP signing was required. An attacker opens a signed LDAP connection with a session key they know, relays a victim’s NTLM authentication over that connection, and slips a malicious LDAP operation into the same signed message. The domain controller 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 attacks? 

Not on its own. Before the October 2026 update, every Windows Server 2022 and Windows Server 2025 domain controller we tested accepted relayed NTLM authentication with LDAP signing required. Install the update, enforce LDAP signing and channel binding together, and reduce NTLM wherever you can.

Does the LDAP signing bypass work against LDAPS? 

The technique targets LDAP on 389/TCP. If 389/TCP is blocked then this technique will not work against LDAPS (636/TCP).

Which Windows Server versions were vulnerable? 

We tested against Server 2016 domain controller and above, and it was vulnerable until the October 2026 cumulative update.

Does the attacker need domain credentials? 

Not necessarily. The attacker needs a signed LDAP session with a session key they know. That can come from valid domain account credentials or, where the domain controller accepts it, an anonymous LDAP connection. The attacker also needs to coerce a victim into authenticating to them.

What can an attacker do with the LDAP signing bypass? 

The attacker can make a domain controller authenticate to the attacker and relay the authentication to another domain controller to compromise the domain instantly. In our example, we relayed a server’s computer account and wrote msDS-AllowedToActOnBehalfOfOtherIdentity on that server to set up resource-based constrained delegation (RBCD).

Has Microsoft patched the LDAP signing bypass? 

Yes. We reported it to Microsoft on October 6, 2026, and Microsoft independently shipped a fix on October 13, 2026 (KB5126646). The patch tracks each LDAP connection’s identity with a counter. If the identity changes partway through a signed SASL record, the domain controller discards the rest of that SASL record, so the stacked operation never runs. Microsoft fixed the related SMB issue in September 2026.

First and foremost, to better understand NTLM and relay attacks in detail please read the following blogs as this blog post will not cover the basics of it:

We independently identified the similar vulnerability in SMB as well. That vulnerability was patched in September 2026. Another researcher also identified this silent patch and published a blog here:

Raj Patel

Consultant

Raj Patel is a Consultant at SpecterOps specializing in Adversary Simulation. He enjoys teaching Adversary Tactics: Identity-Driven Offensive Tradecraft and reverse engineering in his free time.

Ready to get started?

Book a Demo