Detecting NTLM Relay with Event Correlation Authorized Testing Disclaimer : The offensive techniques and attack simulations described in this skill are intended exclusively for authorized penetration testing, red team engagements, purple team exercises, and security research conducted with explicit written permission from the system owner. Unauthorized use of these techniques against systems you do not own or have permission to test is illegal and unethical. Always operate within the scope of your engagement and comply with applicable laws and regulations. Overview NTLM relay attacks intercep…

\n filter_localhost:\n IpAddress:\n - '127.0.0.1'\n - '::1'\n - '-'\n condition: selection and not filter_localhost\nlevel: high\ntags:\n - attack.credential_access\n - attack.t1557.001\n - attack.t1187\nfalsepositives:\n - Legitimate NTLM authentication from machine accounts during failover\n - Cluster service machine account authentication\n```\n\n### Step 8: Build Comprehensive Correlation Dashboard\n\n```spl\n# Splunk: NTLM Relay Detection Dashboard -- Combined Correlation Query\n\n# Panel 1: IP-Hostname Mismatches (Core Relay Indicator)\nindex=wineventlog EventCode=4624 LogonType=3 AuthenticationPackageName=\"NTLM\"\n| where TargetUserName != \"ANONYMOUS LOGON\" AND NOT match(TargetUserName, \".*\\\\$\")\n| eval mismatch=if(lower(WorkstationName) != lower(mvindex(split(IpAddress, \".\"), 0)),\n \"POSSIBLE_MISMATCH\", \"OK\")\n| where mismatch=\"POSSIBLE_MISMATCH\"\n| stats count by TargetUserName WorkstationName IpAddress ComputerName\n\n# Panel 2: NTLMv1 Downgrade Events\nindex=wineventlog EventCode=4624 LmPackageName=\"NTLM V1\"\n| timechart span=1h count by ComputerName\n\n# Panel 3: Machine Account Relay (PetitPotam Indicator)\nindex=wineventlog EventCode=4624 LogonType=3 AuthenticationPackageName=\"NTLM\"\n TargetUserName=\"*$\"\n| stats count values(IpAddress) as relay_sources by TargetUserName ComputerName\n\n# Panel 4: NTLM Authentication Volume Anomaly\nindex=wineventlog EventCode=4624 LogonType=3 AuthenticationPackageName=\"NTLM\"\n| timechart span=15m count\n| streamstats window=20 avg(count) as avg_count stdev(count) as stdev_count\n| eval upper_bound=avg_count + (3 * stdev_count)\n| where count > upper_bound\n\n# Panel 5: SMB Signing Status (from audit results)\n| inputlookup smb_signing_audit.csv\n| stats count by Status\n| table Status count\n```\n\n## Key Concepts\n\n| Term | Definition |\n|------|------------|\n| **NTLM Relay (T1557.001)** | Attack that intercepts NTLM authentication messages and forwards them to a target service, authenticating as the victim without knowing their password |\n| **Event 4624 LogonType 3** | Windows Security Event for successful network logon -- the primary event generated on relay targets; source IP field reveals the relay attacker's address |\n| **IP-Hostname Mismatch** | When Event 4624 WorkstationName field does not correspond to the IpAddress field, indicating the authentication was relayed through a third party |\n| **Responder** | Attack tool that poisons LLMNR (UDP 5355), NBT-NS (UDP 137), and mDNS (UDP 5353) responses to capture NTLM authentication from victims on the local network |\n| **ntlmrelayx** | Fox-IT/Impacket tool that relays captured NTLM authentication to SMB, LDAP, HTTP, MSSQL, and other protocols to gain unauthorized access |\n| **SMB Signing** | Cryptographic signing of SMB packets that prevents relay attacks against SMB services; must be set to \"Required\" (not just \"Enabled\") for protection |\n| **LDAP Signing** | Cryptographic signing of LDAP operations that prevents relay attacks against LDAP services on domain controllers; controlled by LDAPServerIntegrity registry value |\n| **LDAP Channel Binding** | Extended Protection for Authentication (EPA) that binds the NTLM authentication to the TLS channel, preventing relay to LDAPS |\n| **NTLMv1 Downgrade** | Attack forcing authentication from NTLMv2 to the weaker NTLMv1 protocol, which is easier to crack offline and has weaker relay protections |\n| **PetitPotam** | Coercion technique abusing MS-EFSRPC to force a domain controller to authenticate to an attacker-controlled host, enabling relay to AD CS or LDAP |\n| **LmCompatibilityLevel** | Registry setting controlling which NTLM version is used; value of 5 (Send NTLMv2 only, refuse LM and NTLM) provides strongest protection |\n| **Event 8004** | NTLM operational log event on domain controllers showing all NTLM authentication pass-through, critical for auditing NTLM usage before restriction |\n\n## Tools & Systems\n\n| Tool | Purpose |\n|------|---------|\n| **Splunk / Elastic SIEM** | Log aggregation and correlation for Event 4624 analysis, IP-hostname mismatch detection, and NTLM downgrade monitoring |\n| **Microsoft Sentinel** | Cloud SIEM with KQL queries for NTLM relay detection and built-in analytics rules for PetitPotam |\n| **CrowdStrike Falcon Identity Protection** | Detects NTLM relay attacks against domain controller accounts regardless of coercion method used |\n| **Responder** | LLMNR/NBT-NS/mDNS poisoning tool used by attackers -- understanding its behavior is essential for detection |\n| **ntlmrelayx (Impacket)** | Multi-protocol NTLM relay tool developed by Fox-IT -- used in testing and by adversaries |\n| **PingCastle** | Active Directory security assessment tool that audits SMB signing, LDAP signing, and NTLM configuration |\n| **Zeek** | Network security monitor for capturing SMB signing negotiation, LLMNR traffic, and DCE-RPC activity |\n| **Sigma** | Vendor-agnostic detection rule format for portable NTLM relay detection rules |\n\n## Common Scenarios\n\n### Scenario 1: Responder Poisoning with NTLM Relay to File Server\n\n**Context**: A SOC analyst observes multiple Event 4624 LogonType 3 entries on a file server (10.10.20.100) where the WorkstationName field shows different workstation names but the IpAddress field consistently shows 10.10.5.50, a host not in the IT asset inventory.\n\n**Approach**:\n1. Query Event 4624 on 10.10.20.100 filtered for IpAddress=10.10.5.50: find 15 successful NTLM logons in 30 minutes from 8 different user accounts\n2. Cross-reference 10.10.5.50 with DHCP logs and DNS: host is not a registered domain member, MAC address shows a Linux-based NIC\n3. Query Zeek network logs for 10.10.5.50: identify LLMNR responses (UDP 5355) to multiple workstations and SMB connections to 10.10.20.100\n4. Confirm IP-hostname mismatch: WorkstationName values (WS-FINANCE01, WS-HR03, etc.) all resolve to different IPs in DNS, not 10.10.5.50\n5. Check SMB signing on 10.10.20.100: RequireSecuritySignature is False, enabling the relay attack\n6. Contain: block 10.10.5.50 at the switch, force password reset for all 8 affected accounts, enable SMB signing on the file server\n7. Remediate: disable LLMNR and NBT-NS via GPO, enforce SMB signing domain-wide\n\n**Pitfalls**:\n- Dismissing the multiple logons as normal network activity without checking the IP-hostname correlation\n- Not checking SMB signing status on the target server to understand why the relay succeeded\n- Only resetting the password for one user instead of all accounts that were relayed\n\n### Scenario 2: PetitPotam Relay to AD Certificate Services\n\n**Context**: During a threat hunt, an analyst finds Event 4624 LogonType 3 on the AD CS server (ADCS01) showing the domain controller machine account (DC01$) authenticating via NTLM from IP 10.10.5.50, which is not the DC's IP address (10.10.1.10).\n\n**Approach**:\n1. Confirm the anomaly: DC01$ should only authenticate from 10.10.1.10, but Event 4624 shows authentication from 10.10.5.50 via NTLM (not Kerberos)\n2. Check for certificate enrollment: query AD CS logs for certificate requests from DC01$ around the same timestamp -- find a certificate issued for DC01$\n3. Identify the attack: PetitPotam coerced DC01 to authenticate to 10.10.5.50, which relayed the authentication to ADCS01 to request a certificate for DC01$\n4. Assess impact: with a DC certificate, the attacker can authenticate as DC01$ and perform DCSync to extract all domain credentials\n5. Revoke the fraudulently issued certificate immediately\n6. Check for DCSync activity: query Event 4662 for directory replication from non-DC sources\n7. Contain: isolate 10.10.5.50, revoke certificate, patch EFS (MS-EFSRPC), enforce EPA on AD CS, require LDAP signing on all DCs\n\n**Pitfalls**:\n- Not recognizing that machine account NTLM authentication from an unexpected IP is a critical indicator of coercion + relay\n- Failing to check AD CS for fraudulent certificate issuance, which represents the actual objective of the attack\n- Not auditing LDAP signing and EPA on AD CS servers, which would have prevented the relay\n\n## Output Format\n\n```\nHunt ID: TH-NTLM-RELAY-[DATE]-[SEQ]\nAlert Severity: Critical\nMITRE Technique: T1557.001 (LLMNR/NBT-NS Poisoning and SMB Relay)\n\nRelay Indicators:\n Victim Account: [Domain\\Username or Machine$]\n WorkstationName: [Victim hostname from Event 4624]\n Expected Source IP: [IP matching WorkstationName in DNS/DHCP]\n Actual Source IP: [Attacker/relay IP from Event 4624 IpAddress field]\n Target Host: [Server receiving the relayed authentication]\n\nAuthentication Details:\n Event ID: 4624\n LogonType: 3 (Network)\n AuthenticationPackage: NTLM\n LmPackageName: [NTLM V1 or NTLM V2]\n LogonProcess: [NtLmSsp]\n Timestamp: [Event time]\n\nSigning Status:\n Target SMB Signing: [Required/Not Required]\n Target LDAP Signing: [Required/Not Required]\n LDAP Channel Binding: [Required/Not Required]\n\nPoisoning Evidence:\n LLMNR Activity: [Detected/Not Detected from relay IP]\n NBT-NS Activity: [Detected/Not Detected from relay IP]\n Coercion Method: [PetitPotam/DFSCoerce/PrinterBug/Unknown]\n\nRisk Assessment: [Critical - relay from DC / High - relay from user account]\nRecommended Actions:\n - Immediate: [Block relay IP, reset affected credentials]\n - Short-term: [Enable SMB/LDAP signing, disable LLMNR/NBT-NS]\n - Long-term: [Migrate to Kerberos, enforce EPA, restrict NTLM via GPO]\n```\n---","attachment_filenames":["references/api-reference.md","scripts/agent.py","scripts/detect_ntlm_relay.py"],"attachments":[{"filename":"references/api-reference.md","content":"# NTLM Relay Detection API Reference\n\n## MITRE ATT&CK Mapping\n\n| Technique | ID | Description |\n|-----------|----|-------------|\n| LLMNR/NBT-NS Poisoning and SMB Relay | T1557.001 | Poisoning name resolution to capture and relay NTLM auth |\n| Forced Authentication | T1187 | Coercing systems to authenticate (PetitPotam, PrinterBug) |\n| Adversary-in-the-Middle | T1557 | Parent technique for relay and poisoning attacks |\n| Exploitation for Credential Access | T1212 | Exploiting protocol weaknesses for credential theft |\n\n## Windows Security Event IDs for NTLM Relay Detection\n\n| Event ID | Log | Relay Significance |\n|----------|-----|--------------------|\n| 4624 (Type 3) | Security | Network logon -- primary relay detection event. Check IP vs WorkstationName |\n| 4625 | Security | Failed logon -- relay failures leave traces here |\n| 4648 | Security | Explicit credential logon -- may appear in some relay scenarios |\n| 4776 | Security | NTLM credential validation on domain controller |\n| 8001 | NTLM Operational | NTLM client blocked audit |\n| 8002 | NTLM Operational | NTLM server blocked audit |\n| 8003 | NTLM Operational | NTLM server blocked in domain |\n| 8004 | NTLM Operational | NTLM authentication to DC audit (critical for inventory) |\n\n## Event 4624 Key Fields for Relay Detection\n\n| Field | Normal Value | Relay Indicator |\n|-------|-------------|-----------------|\n| LogonType | 3 | Always 3 for network relay |\n| AuthenticationPackageName | NTLM | Must be NTLM (Kerberos cannot be relayed) |\n| LmPackageName | NTLM V2 | NTLM V1 indicates downgrade attack |\n| WorkstationName | Victim hostname | Name of victim machine (not the relay host) |\n| IpAddress | Victim IP | Attacker/relay IP (MISMATCH = relay indicator) |\n| LogonProcessName | NtLmSsp | Standard for NTLM logon |\n| ImpersonationLevel | Delegation/Impersonation | High privilege relay |\n\n## SMB Signing Registry Keys\n\n| Registry Path | Value | Secure Setting |\n|--------------|-------|---------------|\n| HKLM\\SYSTEM\\CurrentControlSet\\Services\\LanManServer\\Parameters\\RequireSecuritySignature | REG_DWORD | 1 (Required) |\n| HKLM\\SYSTEM\\CurrentControlSet\\Services\\LanManServer\\Parameters\\EnableSecuritySignature | REG_DWORD | 1 (Enabled) |\n| HKLM\\SYSTEM\\CurrentControlSet\\Services\\LanManWorkstation\\Parameters\\RequireSecuritySignature | REG_DWORD | 1 (Required) |\n\n## LDAP Signing Registry Keys (Domain Controllers)\n\n| Registry Path | Value | Meaning |\n|--------------|-------|---------|\n| HKLM\\SYSTEM\\CurrentControlSet\\Services\\NTDS\\Parameters\\LDAPServerIntegrity | 0 | None (Vulnerable) |\n| | 1 | Negotiate (Default - Vulnerable) |\n| | 2 | Required (Secure) |\n| HKLM\\SYSTEM\\CurrentControlSet\\Services\\NTDS\\Parameters\\LdapEnforceChannelBinding | 0 | Disabled (Vulnerable) |\n| | 1 | When Supported |\n| | 2 | Always Required (Secure) |\n\n## NTLM Configuration Registry Keys\n\n| Registry Path | Value | Meaning |\n|--------------|-------|---------|\n| HKLM\\SYSTEM\\CurrentControlSet\\Control\\Lsa\\LmCompatibilityLevel | 0 | Send LM & NTLM (Most Vulnerable) |\n| | 1 | Send LM & NTLM, NTLMv2 session if negotiated |\n| | 2 | Send NTLM only |\n| | 3 | Send NTLMv2 only (Recommended minimum) |\n| | 4 | Send NTLMv2 only, refuse LM |\n| | 5 | Send NTLMv2 only, refuse LM & NTLM (Most Secure) |\n| HKLM\\SOFTWARE\\Policies\\Microsoft\\Windows NT\\DNSClient\\EnableMulticast | 0 | LLMNR Disabled (Secure) |\n| | 1 | LLMNR Enabled (Vulnerable to Responder) |\n\n## Network Indicators\n\n| Protocol | Port | Attack Role |\n|----------|------|-------------|\n| UDP | 5355 | LLMNR -- Responder poisoning target |\n| UDP | 137 | NBT-NS -- Responder poisoning target |\n| UDP | 5353 | mDNS -- Responder poisoning target |\n| TCP | 445 | SMB -- relay target (if signing not enforced) |\n| TCP | 389 | LDAP -- relay target (if signing not enforced) |\n| TCP | 636 | LDAPS -- relay target (if channel binding not enforced) |\n| TCP | 80/443 | HTTP(S) -- relay target for AD CS enrollment |\n| TCP | 135 | RPC -- used for coercion (PetitPotam, PrinterBug) |\n\n## Coercion Methods\n\n| Method | Protocol | Vulnerability | Target |\n|--------|----------|--------------|--------|\n| PetitPotam | MS-EFSRPC | CVE-2021-36942 | Domain controllers -> AD CS |\n| DFSCoerce | MS-DFSNM | N/A | Domain controllers |\n| PrinterBug (SpoolSample) | MS-RPRN | By design | Any host with Print Spooler |\n| ShadowCoerce | MS-FSRVP | N/A | Hosts with File Server VSS Agent |\n\n## Splunk SPL - NTLM Relay Detection Queries\n\n```spl\n# IP-hostname mismatch detection\nindex=wineventlog EventCode=4624 LogonType=3 AuthenticationPackageName=\"NTLM\"\n| where TargetUserName != \"ANONYMOUS LOGON\"\n| lookup dns_inventory hostname AS WorkstationName OUTPUT expected_ip\n| where isnotnull(expected_ip) AND IpAddress != expected_ip\n| table _time ComputerName TargetUserName WorkstationName IpAddress expected_ip\n\n# NTLMv1 downgrade detection\nindex=wineventlog EventCode=4624 LmPackageName=\"NTLM V1\"\n| where TargetUserName != \"ANONYMOUS LOGON\"\n| stats count by TargetUserName IpAddress ComputerName\n\n# Machine account relay (PetitPotam indicator)\nindex=wineventlog EventCode=4624 LogonType=3 AuthenticationPackageName=\"NTLM\"\n TargetUserName=\"*$\"\n| stats dc(IpAddress) as source_count values(IpAddress) as sources by TargetUserName\n| where source_count > 1\n```\n\n## KQL - Microsoft Sentinel Queries\n\n```kql\n// NTLM relay IP-hostname mismatch\nSecurityEvent\n| where EventID == 4624 and LogonType == 3\n| where AuthenticationPackageName == \"NTLM\"\n| where TargetUserName !endswith \"$\" and TargetUserName != \"ANONYMOUS LOGON\"\n| where IpAddress != \"-\" and IpAddress != \"127.0.0.1\"\n| project TimeGenerated, Computer, TargetUserName, WorkstationName, IpAddress, LmPackageName\n\n// NTLMv1 downgrade detection\nSecurityEvent\n| where EventID == 4624 and LmPackageName == \"NTLM V1\"\n| where TargetUserName !endswith \"$\"\n| summarize Count=count() by TargetUserName, IpAddress, Computer\n```\n\n## python-evtx - Parse Security EVTX\n\n```python\nfrom Evtx.Evtx import FileHeader\nfrom lxml import etree\n\nNS = {\"evt\": \"http://schemas.microsoft.com/win/2004/08/events/event\"}\nwith open(\"Security.evtx\", \"rb\") as f:\n fh = FileHeader(f)\n for record in fh.records():\n root = etree.fromstring(record.xml().encode(\"utf-8\"))\n eid = root.find(\".//evt:System/evt:EventID\", NS)\n if eid is not None and eid.text == \"4624\":\n data = {e.get(\"Name\"): e.text for e in root.findall(\".//evt:EventData/evt:Data\", NS)}\n if data.get(\"AuthenticationPackageName\") == \"NTLM\" and data.get(\"LogonType\") == \"3\":\n print(data.get(\"TargetUserName\"), data.get(\"WorkstationName\"), data.get(\"IpAddress\"))\n```\n\n## References\n\n- Fox-IT ntlmrelayx: https://blog.fox-it.com/2017/05/09/relaying-credentials-everywhere-with-ntlmrelayx/\n- Fox-IT PetitPotam Detection: https://www.fox-it.com/nl-en/research-blog/detecting-and-hunting-for-the-petitpotam-ntlm-relay-attack/\n- CrowdStrike NTLM Relay Detection: https://www.crowdstrike.com/en-us/blog/how-to-detect-domain-controller-account-relay-attacks-with-crowdstrike-identity-protection/\n- NCC Group PetitPotam: https://www.nccgroup.com/research-blog/detecting-and-hunting-for-the-petitpotam-ntlm-relay-attack/\n- HackTheBox NTLM Relay Detection: https://www.hackthebox.com/blog/ntlm-relay-attack-detection\n- Microsoft NTLMv1 Detection: https://dirteam.com/sander/2022/06/15/howto-detect-ntlmv1-authentication/\n- MITRE T1557.001: https://attack.mitre.org/techniques/T1557/001/\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":7407,"content_sha256":"f851494565bc4d5ab6d87272bbcab6402df737f6bf89db54fe8c480aeaa72ce0"},{"filename":"scripts/agent.py","content":"#!/usr/bin/env python3\n\"\"\"NTLM Relay Detection Agent - Detects NTLM relay via Event 4624 correlation and signing audit.\"\"\"\n\nimport json\nimport logging\nimport argparse\nimport csv\nimport os\nimport sys\nimport subprocess\nfrom collections import defaultdict\nfrom datetime import datetime, timedelta\n\nlogging.basicConfig(level=logging.INFO, format=\"%(asctime)s [%(levelname)s] %(message)s\")\nlogger = logging.getLogger(__name__)\n\nEVTX_NS = \"http://schemas.microsoft.com/win/2004/08/events/event\"\nRAPID_AUTH_WINDOW_DEFAULT = 120\nRAPID_AUTH_THRESHOLD_DEFAULT = 3\nSUBPROCESS_TIMEOUT = 30\n\n\ndef parse_security_evtx(evtx_path):\n \"\"\"Parse Windows Security EVTX for Event 4624/4625/4776.\"\"\"\n try:\n from Evtx.Evtx import FileHeader\n from lxml import etree\n except ImportError:\n logger.error(\"Required packages missing. Install: pip install python-evtx lxml\")\n sys.exit(1)\n\n events = []\n target_ids = {\"4624\", \"4625\", \"4776\"}\n ns = {\"evt\": EVTX_NS}\n with open(evtx_path, \"rb\") as f:\n fh = FileHeader(f)\n for record in fh.records():\n try:\n xml = record.xml()\n root = etree.fromstring(xml.encode(\"utf-8\"))\n eid_elem = root.find(\".//evt:System/evt:EventID\", ns)\n if eid_elem is None or eid_elem.text not in target_ids:\n continue\n data = {}\n for elem in root.findall(\".//evt:EventData/evt:Data\", ns):\n data[elem.get(\"Name\", \"\")] = elem.text or \"\"\n time_elem = root.find(\".//evt:System/evt:TimeCreated\", ns)\n data[\"TimeCreated\"] = time_elem.get(\"SystemTime\", \"\") if time_elem is not None else \"\"\n comp_elem = root.find(\".//evt:System/evt:Computer\", ns)\n data[\"Computer\"] = comp_elem.text if comp_elem is not None else \"\"\n data[\"EventID\"] = eid_elem.text\n events.append(data)\n except Exception:\n continue\n logger.info(\"Parsed %d security events from %s\", len(events), evtx_path)\n return events\n\n\ndef load_inventory(csv_path):\n \"\"\"Load hostname-to-IP inventory from CSV (columns: hostname, ip_address).\"\"\"\n inventory = {}\n try:\n with open(csv_path, \"r\", newline=\"\") as f:\n reader = csv.DictReader(f)\n for row in reader:\n hostname = row.get(\"hostname\", \"\").strip().upper()\n ip = row.get(\"ip_address\", \"\").strip()\n if hostname and ip:\n inventory[hostname] = ip\n except Exception as e:\n logger.error(\"Failed to load inventory: %s\", e)\n logger.info(\"Loaded %d hosts from inventory\", len(inventory))\n return inventory\n\n\ndef detect_ip_hostname_mismatch(events, inventory):\n \"\"\"Detect NTLM relay via IP-hostname mismatch in Event 4624 LogonType 3.\"\"\"\n findings = []\n for ev in events:\n if ev.get(\"EventID\") != \"4624\" or ev.get(\"LogonType\") != \"3\":\n continue\n if ev.get(\"AuthenticationPackageName\") != \"NTLM\":\n continue\n user = ev.get(\"TargetUserName\", \"\")\n if user.endswith(\"$\") or user in (\"ANONYMOUS LOGON\", \"-\", \"\"):\n continue\n source_ip = ev.get(\"IpAddress\", \"\")\n if source_ip in (\"-\", \"::1\", \"127.0.0.1\", \"\"):\n continue\n workstation = ev.get(\"WorkstationName\", \"\").strip().upper()\n if workstation in inventory:\n expected = inventory[workstation]\n if source_ip != expected:\n findings.append({\n \"detection\": \"IP-Hostname Mismatch (NTLM Relay Indicator)\",\n \"severity\": \"CRITICAL\",\n \"mitre\": \"T1557.001\",\n \"timestamp\": ev.get(\"TimeCreated\"),\n \"target_host\": ev.get(\"Computer\"),\n \"target_user\": user,\n \"workstation\": workstation,\n \"actual_ip\": source_ip,\n \"expected_ip\": expected,\n \"lm_package\": ev.get(\"LmPackageName\"),\n })\n logger.info(\"IP-hostname mismatch findings: %d\", len(findings))\n return findings\n\n\ndef detect_rapid_auth(events, window=RAPID_AUTH_WINDOW_DEFAULT, threshold=RAPID_AUTH_THRESHOLD_DEFAULT):\n \"\"\"Detect rapid NTLM authentication to multiple targets (relay spraying).\"\"\"\n findings = []\n auth_groups = defaultdict(list)\n for ev in events:\n if ev.get(\"EventID\") != \"4624\" or ev.get(\"LogonType\") != \"3\":\n continue\n if ev.get(\"AuthenticationPackageName\") != \"NTLM\":\n continue\n user = ev.get(\"TargetUserName\", \"\")\n ip = ev.get(\"IpAddress\", \"\")\n if user.endswith(\"$\") or user in (\"ANONYMOUS LOGON\", \"-\", \"\"):\n continue\n if ip in (\"-\", \"::1\", \"127.0.0.1\", \"\"):\n continue\n try:\n ts = datetime.fromisoformat(ev[\"TimeCreated\"].replace(\"Z\", \"+00:00\"))\n except (ValueError, KeyError):\n continue\n auth_groups[(ip, user)].append({\"ts\": ts, \"target\": ev.get(\"Computer\", \"\")})\n\n for (ip, user), auths in auth_groups.items():\n auths.sort(key=lambda x: x[\"ts\"])\n for i in range(len(auths)):\n start = auths[i][\"ts\"]\n end = start + timedelta(seconds=window)\n targets = set()\n for j in range(i, len(auths)):\n if auths[j][\"ts\"] \u003c= end:\n targets.add(auths[j][\"target\"])\n else:\n break\n if len(targets) >= threshold:\n findings.append({\n \"detection\": \"Rapid Multi-Host NTLM Auth (Relay Spraying)\",\n \"severity\": \"HIGH\",\n \"mitre\": \"T1557.001\",\n \"timestamp\": start.isoformat(),\n \"source_ip\": ip,\n \"target_user\": user,\n \"unique_targets\": len(targets),\n \"targets\": sorted(targets),\n \"window_seconds\": window,\n })\n break\n logger.info(\"Rapid auth findings: %d\", len(findings))\n return findings\n\n\ndef detect_ntlmv1_downgrade(events):\n \"\"\"Detect NTLMv1 authentication events indicating downgrade attack.\"\"\"\n findings = []\n v1_by_user = defaultdict(list)\n for ev in events:\n if ev.get(\"EventID\") != \"4624\" or ev.get(\"LogonType\") != \"3\":\n continue\n lm = ev.get(\"LmPackageName\", \"\")\n if \"NTLM V1\" not in lm:\n continue\n user = ev.get(\"TargetUserName\", \"\")\n if user.endswith(\"$\") or user in (\"ANONYMOUS LOGON\", \"-\", \"\"):\n continue\n v1_by_user[user].append({\n \"ts\": ev.get(\"TimeCreated\"),\n \"target\": ev.get(\"Computer\"),\n \"ip\": ev.get(\"IpAddress\"),\n })\n\n for user, auths in v1_by_user.items():\n findings.append({\n \"detection\": \"NTLMv1 Downgrade Detected\",\n \"severity\": \"HIGH\",\n \"mitre\": \"T1557.001\",\n \"timestamp\": auths[0][\"ts\"],\n \"target_user\": user,\n \"ntlmv1_count\": len(auths),\n \"source_ips\": sorted(set(a[\"ip\"] for a in auths)),\n \"targets\": sorted(set(a[\"target\"] for a in auths)),\n })\n logger.info(\"NTLMv1 downgrade findings: %d\", len(findings))\n return findings\n\n\ndef detect_machine_relay(events):\n \"\"\"Detect machine account NTLM relay (PetitPotam, DFSCoerce, PrinterBug).\"\"\"\n findings = []\n machine_auths = defaultdict(list)\n for ev in events:\n if ev.get(\"EventID\") != \"4624\" or ev.get(\"LogonType\") != \"3\":\n continue\n if ev.get(\"AuthenticationPackageName\") != \"NTLM\":\n continue\n user = ev.get(\"TargetUserName\", \"\")\n if not user.endswith(\"$\"):\n continue\n ip = ev.get(\"IpAddress\", \"\")\n if ip in (\"-\", \"::1\", \"127.0.0.1\", \"\"):\n continue\n machine_auths[user].append({\n \"ts\": ev.get(\"TimeCreated\"),\n \"target\": ev.get(\"Computer\"),\n \"ip\": ip,\n })\n\n for machine, auths in machine_auths.items():\n ips = set(a[\"ip\"] for a in auths)\n if len(ips) > 1:\n findings.append({\n \"detection\": \"Machine Account Relay (Coercion + NTLM Relay)\",\n \"severity\": \"CRITICAL\",\n \"mitre\": \"T1557.001\",\n \"timestamp\": auths[0][\"ts\"],\n \"machine_account\": machine,\n \"source_ips\": sorted(ips),\n \"targets\": sorted(set(a[\"target\"] for a in auths)),\n \"auth_count\": len(auths),\n })\n logger.info(\"Machine account relay findings: %d\", len(findings))\n return findings\n\n\ndef audit_smb_signing_local():\n \"\"\"Audit local SMB signing configuration (Windows only).\"\"\"\n if sys.platform != \"win32\":\n logger.info(\"SMB signing audit only available on Windows\")\n return {}\n\n audit = {}\n checks = {\n \"SMB_Server_RequireSign\": (\n r\"HKLM\\SYSTEM\\CurrentControlSet\\Services\\LanManServer\\Parameters\",\n \"RequireSecuritySignature\"\n ),\n \"SMB_Client_RequireSign\": (\n r\"HKLM\\SYSTEM\\CurrentControlSet\\Services\\LanManWorkstation\\Parameters\",\n \"RequireSecuritySignature\"\n ),\n \"LmCompatibilityLevel\": (\n r\"HKLM\\SYSTEM\\CurrentControlSet\\Control\\Lsa\",\n \"LmCompatibilityLevel\"\n ),\n \"LLMNR_Disabled\": (\n r\"HKLM\\SOFTWARE\\Policies\\Microsoft\\Windows NT\\DNSClient\",\n \"EnableMulticast\"\n ),\n }\n\n for label, (key, value_name) in checks.items():\n try:\n result = subprocess.run(\n [\"reg\", \"query\", key, \"/v\", value_name],\n capture_output=True, text=True, timeout=SUBPROCESS_TIMEOUT\n )\n if result.returncode == 0:\n for line in result.stdout.splitlines():\n if value_name in line:\n parts = line.strip().split()\n audit[label] = parts[-1] if parts else \"UNKNOWN\"\n break\n else:\n audit[label] = \"NOT_CONFIGURED\"\n except subprocess.TimeoutExpired:\n audit[label] = \"TIMEOUT\"\n except Exception as e:\n audit[label] = f\"ERROR: {e}\"\n\n # Evaluate risk\n smb_server = audit.get(\"SMB_Server_RequireSign\", \"\")\n audit[\"SMB_Relay_Vulnerable\"] = \"YES\" if smb_server != \"0x1\" else \"NO\"\n\n lm_level = audit.get(\"LmCompatibilityLevel\", \"\")\n try:\n lm_int = int(lm_level, 0)\n audit[\"NTLMv1_Vulnerable\"] = \"YES\" if lm_int \u003c 3 else \"NO\"\n except (ValueError, TypeError):\n audit[\"NTLMv1_Vulnerable\"] = \"UNKNOWN\"\n\n llmnr = audit.get(\"LLMNR_Disabled\", \"\")\n audit[\"Responder_Vulnerable\"] = \"NO\" if llmnr == \"0x0\" else \"YES\"\n\n return audit\n\n\ndef generate_report(all_findings, smb_audit, output_path):\n \"\"\"Generate JSON detection report.\"\"\"\n report = {\n \"scan_timestamp\": datetime.utcnow().isoformat() + \"Z\",\n \"mitre_technique\": \"T1557.001\",\n \"summary\": {\n \"total_findings\": len(all_findings),\n \"critical\": len([f for f in all_findings if f.get(\"severity\") == \"CRITICAL\"]),\n \"high\": len([f for f in all_findings if f.get(\"severity\") == \"HIGH\"]),\n \"medium\": len([f for f in all_findings if f.get(\"severity\") == \"MEDIUM\"]),\n },\n \"findings\": all_findings,\n \"smb_signing_audit\": smb_audit,\n }\n\n with open(output_path, \"w\") as f:\n json.dump(report, f, indent=2, default=str)\n logger.info(\"Report saved to %s\", output_path)\n\n s = report[\"summary\"]\n print(f\"\\nNTLM RELAY DETECTION REPORT\")\n print(f\" Total findings: {s['total_findings']}\")\n print(f\" Critical: {s['critical']}, High: {s['high']}, Medium: {s['medium']}\")\n if s[\"critical\"] > 0:\n print(\" [!!!] CRITICAL: IP-hostname mismatch or machine account relay detected\")\n if smb_audit.get(\"SMB_Relay_Vulnerable\") == \"YES\":\n print(\" [!] WARNING: SMB signing NOT enforced on this host\")\n if smb_audit.get(\"Responder_Vulnerable\") == \"YES\":\n print(\" [!] WARNING: LLMNR enabled - vulnerable to Responder poisoning\")\n return report\n\n\ndef main():\n parser = argparse.ArgumentParser(\n description=\"NTLM Relay Detection Agent (T1557.001)\"\n )\n parser.add_argument(\"--evtx\", required=True, help=\"Path to Windows Security .evtx file\")\n parser.add_argument(\"--inventory\", help=\"CSV file with hostname,ip_address columns for mismatch detection\")\n parser.add_argument(\"--output\", \"-o\", default=\"ntlm_relay_report.json\",\n help=\"Output JSON report path (default: ntlm_relay_report.json)\")\n parser.add_argument(\"--rapid-window\", type=int, default=RAPID_AUTH_WINDOW_DEFAULT,\n help=f\"Rapid auth detection window in seconds (default: {RAPID_AUTH_WINDOW_DEFAULT})\")\n parser.add_argument(\"--rapid-threshold\", type=int, default=RAPID_AUTH_THRESHOLD_DEFAULT,\n help=f\"Min unique targets for rapid auth alert (default: {RAPID_AUTH_THRESHOLD_DEFAULT})\")\n parser.add_argument(\"--audit-signing\", action=\"store_true\",\n help=\"Audit local SMB/NTLM signing configuration (Windows only)\")\n parser.add_argument(\"--verbose\", \"-v\", action=\"store_true\", help=\"Enable debug logging\")\n args = parser.parse_args()\n\n if args.verbose:\n logging.getLogger().setLevel(logging.DEBUG)\n\n if not os.path.isfile(args.evtx):\n logger.error(\"EVTX file not found: %s\", args.evtx)\n sys.exit(1)\n\n inventory = {}\n if args.inventory:\n if os.path.isfile(args.inventory):\n inventory = load_inventory(args.inventory)\n else:\n logger.warning(\"Inventory file not found: %s\", args.inventory)\n\n logger.info(\"Parsing security events from: %s\", args.evtx)\n events = parse_security_evtx(args.evtx)\n\n mismatch = detect_ip_hostname_mismatch(events, inventory) if inventory else []\n rapid = detect_rapid_auth(events, args.rapid_window, args.rapid_threshold)\n downgrade = detect_ntlmv1_downgrade(events)\n machine = detect_machine_relay(events)\n\n if not inventory:\n logger.warning(\"No inventory provided (--inventory). IP-hostname mismatch detection disabled.\")\n\n all_findings = mismatch + machine + rapid + downgrade\n all_findings.sort(key=lambda x: {\"CRITICAL\": 0, \"HIGH\": 1, \"MEDIUM\": 2, \"LOW\": 3}.get(\n x.get(\"severity\", \"LOW\"), 4))\n\n smb_audit = audit_smb_signing_local() if args.audit_signing else {}\n\n generate_report(all_findings, smb_audit, args.output)\n\n\nif __name__ == \"__main__\":\n main()\n","content_type":"text/x-python; charset=utf-8","language":"python","size":14721,"content_sha256":"f79da93faf552dceb831fd77114d7dd8891943296c67a89bdcc56834c9daace5"},{"filename":"scripts/detect_ntlm_relay.py","content":"#!/usr/bin/env python3\n\"\"\"\nNTLM Relay Detection via Event Correlation Script\nParses Windows Security event logs to detect NTLM relay attacks through\nIP-hostname mismatch analysis, NTLMv1 downgrade detection, rapid multi-host\nauthentication patterns, and machine account relay indicators.\n\nMITRE ATT&CK: T1557.001 (LLMNR/NBT-NS Poisoning and SMB Relay)\n\nUsage:\n python detect_ntlm_relay.py --evtx \u003csecurity.evtx>\n python detect_ntlm_relay.py --evtx \u003csecurity.evtx> --inventory hosts.csv\n python detect_ntlm_relay.py --evtx \u003csecurity.evtx> --json --output results.json\n\nRequirements:\n pip install python-evtx lxml\n\"\"\"\n\nimport argparse\nimport csv\nimport json\nimport sys\nimport os\nfrom datetime import datetime, timedelta\nfrom collections import defaultdict\n\ntry:\n import Evtx.Evtx as evtx\n from lxml import etree\nexcept ImportError:\n print(\"[!] Required packages not found. Install with: pip install python-evtx lxml\")\n sys.exit(1)\n\n\nEVENT_NS = \"http://schemas.microsoft.com/win/2004/08/events/event\"\n\n# Default time window for rapid authentication detection (seconds)\nRAPID_AUTH_WINDOW = 120\n# Minimum number of unique targets to flag rapid authentication\nRAPID_AUTH_THRESHOLD = 3\n\n\ndef parse_security_event(record_xml):\n \"\"\"Parse a Windows Security event record XML into a dictionary.\"\"\"\n try:\n root = etree.fromstring(record_xml)\n except etree.XMLSyntaxError:\n return None\n\n ns = {\"e\": EVENT_NS}\n event = {}\n\n system = root.find(\".//e:System\", ns)\n if system is not None:\n event_id_elem = system.find(\"e:EventID\", ns)\n event[\"EventID\"] = int(event_id_elem.text) if event_id_elem is not None else 0\n time_elem = system.find(\"e:TimeCreated\", ns)\n if time_elem is not None:\n event[\"TimeCreated\"] = time_elem.get(\"SystemTime\", \"\")\n computer_elem = system.find(\"e:Computer\", ns)\n event[\"Computer\"] = computer_elem.text if computer_elem is not None else \"\"\n\n event_data = root.find(\".//e:EventData\", ns)\n if event_data is not None:\n for data in event_data.findall(\"e:Data\", ns):\n name = data.get(\"Name\", \"\")\n value = data.text or \"\"\n event[name] = value\n\n return event\n\n\ndef load_host_inventory(csv_path):\n \"\"\"\n Load hostname-to-IP mapping from CSV file.\n Expected columns: hostname,ip_address\n \"\"\"\n inventory = {}\n try:\n with open(csv_path, \"r\", newline=\"\") as f:\n reader = csv.DictReader(f)\n for row in reader:\n hostname = row.get(\"hostname\", \"\").strip().upper()\n ip = row.get(\"ip_address\", \"\").strip()\n if hostname and ip:\n inventory[hostname] = ip\n except Exception as e:\n print(f\"[!] Error loading inventory from {csv_path}: {e}\")\n return inventory\n\n\ndef is_internal_ip(ip):\n \"\"\"Check if an IP address is in RFC1918 private ranges.\"\"\"\n if not ip or ip in (\"-\", \"::1\", \"127.0.0.1\"):\n return False\n parts = ip.split(\".\")\n if len(parts) != 4:\n return False\n try:\n first = int(parts[0])\n second = int(parts[1])\n if first == 10:\n return True\n if first == 172 and 16 \u003c= second \u003c= 31:\n return True\n if first == 192 and second == 168:\n return True\n except ValueError:\n return False\n return False\n\n\ndef detect_ip_hostname_mismatch(events, inventory):\n \"\"\"\n Detect NTLM relay by finding Event 4624 LogonType 3 entries where\n the WorkstationName does not match the expected IP for that hostname.\n \"\"\"\n findings = []\n\n for event in events:\n if event.get(\"EventID\") != 4624:\n continue\n if event.get(\"LogonType\") != \"3\":\n continue\n if event.get(\"AuthenticationPackageName\") != \"NTLM\":\n continue\n\n target_user = event.get(\"TargetUserName\", \"\")\n workstation = event.get(\"WorkstationName\", \"\").strip().upper()\n source_ip = event.get(\"IpAddress\", \"\")\n computer = event.get(\"Computer\", \"\")\n timestamp = event.get(\"TimeCreated\", \"\")\n lm_package = event.get(\"LmPackageName\", \"\")\n\n # Skip machine accounts and anonymous logons\n if target_user.endswith(\"$\") or target_user in (\"ANONYMOUS LOGON\", \"-\", \"\"):\n continue\n if source_ip in (\"-\", \"::1\", \"127.0.0.1\", \"\"):\n continue\n\n # Check against inventory\n if workstation in inventory:\n expected_ip = inventory[workstation]\n if source_ip != expected_ip:\n findings.append({\n \"timestamp\": timestamp,\n \"detection_type\": \"IP-Hostname Mismatch (NTLM Relay Indicator)\",\n \"severity\": \"CRITICAL\",\n \"mitre\": \"T1557.001\",\n \"target_host\": computer,\n \"target_user\": target_user,\n \"workstation_name\": workstation,\n \"actual_source_ip\": source_ip,\n \"expected_source_ip\": expected_ip,\n \"lm_package\": lm_package,\n \"explanation\": (\n f\"Event 4624 shows {target_user} authenticating from \"\n f\"workstation '{workstation}' but source IP is {source_ip} \"\n f\"(expected {expected_ip}). This IP mismatch is a primary \"\n f\"indicator of NTLM relay.\"\n ),\n })\n\n return findings\n\n\ndef detect_rapid_multi_host_auth(events, window_seconds=RAPID_AUTH_WINDOW,\n threshold=RAPID_AUTH_THRESHOLD):\n \"\"\"\n Detect rapid NTLM authentication to multiple targets from the same source,\n indicating relay spraying or credential relay.\n \"\"\"\n findings = []\n\n # Group events by source IP and user\n auth_by_source = defaultdict(list)\n\n for event in events:\n if event.get(\"EventID\") != 4624:\n continue\n if event.get(\"LogonType\") != \"3\":\n continue\n if event.get(\"AuthenticationPackageName\") != \"NTLM\":\n continue\n\n target_user = event.get(\"TargetUserName\", \"\")\n source_ip = event.get(\"IpAddress\", \"\")\n\n if target_user.endswith(\"$\") or target_user in (\"ANONYMOUS LOGON\", \"-\", \"\"):\n continue\n if source_ip in (\"-\", \"::1\", \"127.0.0.1\", \"\"):\n continue\n\n try:\n ts = datetime.fromisoformat(event[\"TimeCreated\"].replace(\"Z\", \"+00:00\"))\n except (ValueError, KeyError):\n continue\n\n key = (source_ip, target_user)\n auth_by_source[key].append({\n \"timestamp\": ts,\n \"target_host\": event.get(\"Computer\", \"\"),\n \"workstation\": event.get(\"WorkstationName\", \"\"),\n })\n\n # Analyze each source for rapid multi-host authentication\n for (source_ip, target_user), auth_list in auth_by_source.items():\n auth_list.sort(key=lambda x: x[\"timestamp\"])\n\n # Sliding window analysis\n for i in range(len(auth_list)):\n window_start = auth_list[i][\"timestamp\"]\n window_end = window_start + timedelta(seconds=window_seconds)\n\n targets_in_window = set()\n events_in_window = []\n\n for j in range(i, len(auth_list)):\n if auth_list[j][\"timestamp\"] \u003c= window_end:\n targets_in_window.add(auth_list[j][\"target_host\"])\n events_in_window.append(auth_list[j])\n else:\n break\n\n if len(targets_in_window) >= threshold:\n findings.append({\n \"timestamp\": window_start.isoformat(),\n \"detection_type\": \"Rapid Multi-Host NTLM Authentication (Relay Spraying)\",\n \"severity\": \"HIGH\",\n \"mitre\": \"T1557.001\",\n \"source_ip\": source_ip,\n \"target_user\": target_user,\n \"unique_targets\": len(targets_in_window),\n \"target_hosts\": sorted(targets_in_window),\n \"event_count\": len(events_in_window),\n \"window_seconds\": window_seconds,\n \"explanation\": (\n f\"User '{target_user}' authenticated via NTLM from {source_ip} \"\n f\"to {len(targets_in_window)} unique targets in {window_seconds}s. \"\n f\"Rapid multi-host authentication is consistent with ntlmrelayx spraying.\"\n ),\n })\n break # One finding per source/user pair\n\n return findings\n\n\ndef detect_ntlmv1_downgrade(events):\n \"\"\"\n Detect NTLMv1 authentication which indicates a downgrade attack.\n NTLMv1 is weaker and should not be in use in modern environments.\n \"\"\"\n findings = []\n ntlmv1_by_user = defaultdict(list)\n\n for event in events:\n if event.get(\"EventID\") != 4624:\n continue\n if event.get(\"LogonType\") != \"3\":\n continue\n\n lm_package = event.get(\"LmPackageName\", \"\")\n if \"NTLM V1\" not in lm_package:\n continue\n\n target_user = event.get(\"TargetUserName\", \"\")\n if target_user.endswith(\"$\") or target_user in (\"ANONYMOUS LOGON\", \"-\", \"\"):\n continue\n\n ntlmv1_by_user[target_user].append({\n \"timestamp\": event.get(\"TimeCreated\", \"\"),\n \"computer\": event.get(\"Computer\", \"\"),\n \"source_ip\": event.get(\"IpAddress\", \"\"),\n \"workstation\": event.get(\"WorkstationName\", \"\"),\n })\n\n for user, auth_list in ntlmv1_by_user.items():\n targets = set(a[\"computer\"] for a in auth_list)\n source_ips = set(a[\"source_ip\"] for a in auth_list)\n findings.append({\n \"timestamp\": auth_list[0][\"timestamp\"],\n \"detection_type\": \"NTLMv1 Authentication Detected (Downgrade Attack Indicator)\",\n \"severity\": \"HIGH\",\n \"mitre\": \"T1557.001\",\n \"target_user\": user,\n \"ntlmv1_event_count\": len(auth_list),\n \"source_ips\": sorted(source_ips),\n \"target_hosts\": sorted(targets),\n \"explanation\": (\n f\"User '{user}' authenticated {len(auth_list)} times using NTLMv1. \"\n f\"NTLMv1 is deprecated and should not be in use. This may indicate \"\n f\"a downgrade attack or misconfigured LmCompatibilityLevel.\"\n ),\n })\n\n return findings\n\n\ndef detect_machine_account_relay(events):\n \"\"\"\n Detect machine account NTLM authentication from unexpected IPs,\n indicating PetitPotam, DFSCoerce, or PrinterBug coercion + relay.\n \"\"\"\n findings = []\n machine_auths = defaultdict(list)\n\n for event in events:\n if event.get(\"EventID\") != 4624:\n continue\n if event.get(\"LogonType\") != \"3\":\n continue\n if event.get(\"AuthenticationPackageName\") != \"NTLM\":\n continue\n\n target_user = event.get(\"TargetUserName\", \"\")\n source_ip = event.get(\"IpAddress\", \"\")\n\n # Only machine accounts (ending in $)\n if not target_user.endswith(\"$\"):\n continue\n if source_ip in (\"-\", \"::1\", \"127.0.0.1\", \"\"):\n continue\n\n machine_auths[target_user].append({\n \"timestamp\": event.get(\"TimeCreated\", \"\"),\n \"target_host\": event.get(\"Computer\", \"\"),\n \"source_ip\": source_ip,\n \"workstation\": event.get(\"WorkstationName\", \"\"),\n \"lm_package\": event.get(\"LmPackageName\", \"\"),\n })\n\n for machine_account, auth_list in machine_auths.items():\n source_ips = set(a[\"source_ip\"] for a in auth_list)\n target_hosts = set(a[\"target_host\"] for a in auth_list)\n\n # Flag if machine account authenticates from multiple source IPs\n # or if source IP does not match expected machine IP\n if len(source_ips) > 1:\n findings.append({\n \"timestamp\": auth_list[0][\"timestamp\"],\n \"detection_type\": \"Machine Account NTLM Auth from Multiple Sources (Coercion + Relay)\",\n \"severity\": \"CRITICAL\",\n \"mitre\": \"T1557.001\",\n \"machine_account\": machine_account,\n \"source_ips\": sorted(source_ips),\n \"target_hosts\": sorted(target_hosts),\n \"auth_count\": len(auth_list),\n \"explanation\": (\n f\"Machine account '{machine_account}' authenticated via NTLM from \"\n f\"{len(source_ips)} different source IPs: {', '.join(sorted(source_ips))}. \"\n f\"This indicates the machine's NTLM authentication was coerced \"\n f\"(PetitPotam/DFSCoerce/PrinterBug) and relayed to \"\n f\"{', '.join(sorted(target_hosts))}.\"\n ),\n })\n\n return findings\n\n\ndef detect_anonymous_ntlm_logons(events):\n \"\"\"\n Detect ANONYMOUS LOGON via NTLM which can indicate null session relay\n or Responder activity.\n \"\"\"\n findings = []\n anon_by_ip = defaultdict(list)\n\n for event in events:\n if event.get(\"EventID\") != 4624:\n continue\n if event.get(\"LogonType\") != \"3\":\n continue\n if event.get(\"AuthenticationPackageName\") != \"NTLM\":\n continue\n\n target_user = event.get(\"TargetUserName\", \"\")\n if target_user != \"ANONYMOUS LOGON\":\n continue\n\n source_ip = event.get(\"IpAddress\", \"\")\n if source_ip in (\"-\", \"::1\", \"127.0.0.1\", \"\"):\n continue\n\n anon_by_ip[source_ip].append({\n \"timestamp\": event.get(\"TimeCreated\", \"\"),\n \"target_host\": event.get(\"Computer\", \"\"),\n })\n\n for source_ip, auth_list in anon_by_ip.items():\n targets = set(a[\"target_host\"] for a in auth_list)\n if len(auth_list) >= 3:\n findings.append({\n \"timestamp\": auth_list[0][\"timestamp\"],\n \"detection_type\": \"Excessive ANONYMOUS NTLM Logons (Responder/Relay Probe)\",\n \"severity\": \"MEDIUM\",\n \"mitre\": \"T1557.001\",\n \"source_ip\": source_ip,\n \"anonymous_logon_count\": len(auth_list),\n \"target_hosts\": sorted(targets),\n \"explanation\": (\n f\"Source IP {source_ip} performed {len(auth_list)} anonymous NTLM \"\n f\"logons to {len(targets)} hosts. Excessive anonymous NTLM \"\n f\"authentication may indicate Responder probing or null session relay.\"\n ),\n })\n\n return findings\n\n\ndef parse_evtx_file(filepath):\n \"\"\"Parse a .evtx file and return list of parsed events.\"\"\"\n events = []\n try:\n with evtx.Evtx(filepath) as log:\n for record in log.records():\n try:\n event = parse_security_event(record.xml())\n if event and event.get(\"EventID\") in (4624, 4625, 4648, 4776):\n events.append(event)\n except Exception:\n continue\n except Exception as e:\n print(f\"[!] Error parsing {filepath}: {e}\")\n return events\n\n\ndef print_findings(findings, title):\n \"\"\"Print findings in a formatted table.\"\"\"\n if not findings:\n print(f\"\\n[+] {title}: No findings\")\n return\n\n print(f\"\\n{'=' * 80}\")\n print(f\" {title} ({len(findings)} findings)\")\n print(f\"{'=' * 80}\")\n\n for i, finding in enumerate(findings, 1):\n severity = finding.get(\"severity\", \"N/A\")\n severity_marker = {\n \"CRITICAL\": \"[!!!]\",\n \"HIGH\": \"[!!]\",\n \"MEDIUM\": \"[!]\",\n \"LOW\": \"[.]\",\n }.get(severity, \"[?]\")\n\n print(f\"\\n {severity_marker} [{i}] {finding.get('detection_type', 'Unknown')}\")\n print(f\" Severity: {severity}\")\n print(f\" Time: {finding.get('timestamp', 'N/A')}\")\n\n if \"target_user\" in finding:\n print(f\" User: {finding['target_user']}\")\n if \"machine_account\" in finding:\n print(f\" Machine: {finding['machine_account']}\")\n if \"source_ip\" in finding:\n print(f\" Source IP: {finding['source_ip']}\")\n if \"actual_source_ip\" in finding:\n print(f\" Actual Source IP: {finding['actual_source_ip']}\")\n print(f\" Expected Source IP: {finding.get('expected_source_ip', 'N/A')}\")\n if \"workstation_name\" in finding:\n print(f\" Workstation: {finding['workstation_name']}\")\n if \"target_hosts\" in finding:\n hosts = finding[\"target_hosts\"]\n if len(hosts) \u003c= 5:\n print(f\" Targets: {', '.join(hosts)}\")\n else:\n print(f\" Targets: {', '.join(hosts[:5])} ... (+{len(hosts)-5} more)\")\n if \"source_ips\" in finding:\n print(f\" Source IPs: {', '.join(finding['source_ips'])}\")\n\n print(f\" Detail: {finding.get('explanation', 'N/A')}\")\n\n\ndef main():\n parser = argparse.ArgumentParser(\n description=\"Detect NTLM relay attacks via Windows Security event log correlation\"\n )\n parser.add_argument(\n \"--evtx\", required=True,\n help=\"Path to Windows Security .evtx log file\"\n )\n parser.add_argument(\n \"--inventory\",\n help=\"Path to CSV file with hostname,ip_address columns for mismatch detection\"\n )\n parser.add_argument(\n \"--json\", action=\"store_true\",\n help=\"Output results in JSON format\"\n )\n parser.add_argument(\n \"--output\", \"-o\",\n help=\"Output file path (default: stdout)\"\n )\n parser.add_argument(\n \"--rapid-window\", type=int, default=RAPID_AUTH_WINDOW,\n help=f\"Time window for rapid auth detection in seconds (default: {RAPID_AUTH_WINDOW})\"\n )\n parser.add_argument(\n \"--rapid-threshold\", type=int, default=RAPID_AUTH_THRESHOLD,\n help=f\"Min unique targets for rapid auth alert (default: {RAPID_AUTH_THRESHOLD})\"\n )\n args = parser.parse_args()\n\n if not os.path.exists(args.evtx):\n print(f\"[!] File not found: {args.evtx}\")\n sys.exit(1)\n\n # Load host inventory if provided\n inventory = {}\n if args.inventory:\n if os.path.exists(args.inventory):\n inventory = load_host_inventory(args.inventory)\n print(f\"[*] Loaded {len(inventory)} hosts from inventory\")\n else:\n print(f\"[!] Inventory file not found: {args.inventory}\")\n\n print(f\"[*] Parsing Security events from: {args.evtx}\")\n events = parse_evtx_file(args.evtx)\n print(f\"[*] Parsed {len(events)} relevant Security events (4624, 4625, 4648, 4776)\")\n\n ntlm_4624 = [e for e in events if e.get(\"EventID\") == 4624\n and e.get(\"AuthenticationPackageName\") == \"NTLM\"]\n print(f\"[*] Found {len(ntlm_4624)} NTLM LogonType 3 events for analysis\")\n\n print(\"[*] Running NTLM relay detection modules...\")\n\n # Run all detection modules\n mismatch_findings = detect_ip_hostname_mismatch(events, inventory) if inventory else []\n rapid_auth_findings = detect_rapid_multi_host_auth(\n events, args.rapid_window, args.rapid_threshold\n )\n ntlmv1_findings = detect_ntlmv1_downgrade(events)\n machine_relay_findings = detect_machine_account_relay(events)\n anon_findings = detect_anonymous_ntlm_logons(events)\n\n all_findings = (\n mismatch_findings + rapid_auth_findings + ntlmv1_findings\n + machine_relay_findings + anon_findings\n )\n\n all_results = {\n \"scan_time\": datetime.utcnow().isoformat() + \"Z\",\n \"security_log\": args.evtx,\n \"inventory_file\": args.inventory or \"Not provided\",\n \"inventory_hosts\": len(inventory),\n \"total_events_parsed\": len(events),\n \"ntlm_logon_events\": len(ntlm_4624),\n \"detection_modules\": {\n \"ip_hostname_mismatch\": {\n \"enabled\": bool(inventory),\n \"findings\": mismatch_findings,\n \"count\": len(mismatch_findings),\n },\n \"rapid_multi_host_auth\": {\n \"enabled\": True,\n \"findings\": rapid_auth_findings,\n \"count\": len(rapid_auth_findings),\n \"window_seconds\": args.rapid_window,\n \"threshold\": args.rapid_threshold,\n },\n \"ntlmv1_downgrade\": {\n \"enabled\": True,\n \"findings\": ntlmv1_findings,\n \"count\": len(ntlmv1_findings),\n },\n \"machine_account_relay\": {\n \"enabled\": True,\n \"findings\": machine_relay_findings,\n \"count\": len(machine_relay_findings),\n },\n \"anonymous_ntlm_logons\": {\n \"enabled\": True,\n \"findings\": anon_findings,\n \"count\": len(anon_findings),\n },\n },\n \"summary\": {\n \"total_findings\": len(all_findings),\n \"critical\": len([f for f in all_findings if f.get(\"severity\") == \"CRITICAL\"]),\n \"high\": len([f for f in all_findings if f.get(\"severity\") == \"HIGH\"]),\n \"medium\": len([f for f in all_findings if f.get(\"severity\") == \"MEDIUM\"]),\n \"low\": len([f for f in all_findings if f.get(\"severity\") == \"LOW\"]),\n },\n }\n\n if args.json:\n output = json.dumps(all_results, indent=2, default=str)\n if args.output:\n with open(args.output, \"w\") as f:\n f.write(output)\n print(f\"[*] JSON results written to: {args.output}\")\n else:\n print(output)\n else:\n print(f\"\\n[*] NTLM Relay Detection Report\")\n print(f\"[*] Scan Time: {all_results['scan_time']}\")\n print(f\"[*] Events Analyzed: {all_results['total_events_parsed']}\")\n print(f\"[*] NTLM Network Logons: {all_results['ntlm_logon_events']}\")\n\n if not inventory:\n print(\"\\n[!] WARNING: No host inventory provided (--inventory).\")\n print(\" IP-hostname mismatch detection is DISABLED.\")\n print(\" Provide a CSV with hostname,ip_address columns for full detection.\")\n\n print_findings(mismatch_findings, \"IP-Hostname Mismatch Detection\")\n print_findings(rapid_auth_findings, \"Rapid Multi-Host Authentication\")\n print_findings(ntlmv1_findings, \"NTLMv1 Downgrade Detection\")\n print_findings(machine_relay_findings, \"Machine Account Relay (Coercion)\")\n print_findings(anon_findings, \"Anonymous NTLM Logon Analysis\")\n\n print(f\"\\n{'=' * 80}\")\n print(f\" SUMMARY\")\n print(f\"{'=' * 80}\")\n s = all_results[\"summary\"]\n print(f\" Total Findings: {s['total_findings']}\")\n print(f\" Critical: {s['critical']}\")\n print(f\" High: {s['high']}\")\n print(f\" Medium: {s['medium']}\")\n print(f\" Low: {s['low']}\")\n\n if s[\"critical\"] > 0:\n print(f\"\\n [!!!] CRITICAL findings detected -- NTLM relay attack likely in progress!\")\n print(f\" Recommended: Isolate source IPs, reset affected credentials,\")\n print(f\" enforce SMB/LDAP signing, disable LLMNR/NBT-NS.\")\n\n if args.output:\n with open(args.output, \"w\") as f:\n json.dump(all_results, f, indent=2, default=str)\n print(f\"\\n[*] Full results written to: {args.output}\")\n\n\nif __name__ == \"__main__\":\n main()\n","content_type":"text/x-python; charset=utf-8","language":"python","size":23511,"content_sha256":"81e7d6405975a8b8afe76e229658aac95fdf72f3d201138c75dbdf21a50c4781"}],"content_json":{"type":"doc","content":[{"type":"heading","attrs":{"level":1},"content":[{"text":"Detecting NTLM Relay with Event Correlation","type":"text"}]},{"type":"blockquote","content":[{"type":"paragraph","content":[{"text":"Authorized Testing Disclaimer","type":"text","marks":[{"type":"strong"}]},{"text":": The offensive techniques and attack simulations described in this skill are intended exclusively for authorized penetration testing, red team engagements, purple team exercises, and security research conducted with explicit written permission from the system owner. Unauthorized use of these techniques against systems you do not own or have permission to test is illegal and unethical. Always operate within the scope of your engagement and comply with applicable laws and regulations.","type":"text"}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Overview","type":"text"}]},{"type":"paragraph","content":[{"text":"NTLM relay attacks intercept NTLM authentication messages and forward them to a target service to gain unauthorized access. Attackers use tools like Responder for LLMNR/NBT-NS/mDNS poisoning, ntlmrelayx (Fox-IT/Impacket) for multi-protocol relay, and coercion techniques like PetitPotam (MS-EFSRPC) and DFSCoerce to force authentication from high-value targets like domain controllers. This skill provides a comprehensive event correlation framework using Windows Security Event 4624 LogonType 3 analysis, IP-to-hostname mismatch detection, Responder traffic identification, SMB/LDAP signing audit, and NTLM downgrade detection to identify relay attacks across Active Directory environments.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"When to Use","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Hunting for credential relay activity in Active Directory environments where NTLM authentication is still in use","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Investigating alerts for authentication anomalies where the source IP does not match the expected workstation","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Auditing SMB signing and LDAP signing enforcement to assess exposure to relay attacks","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Detecting NTLM downgrade attacks where NTLMv2 is forced to NTLMv1 for easier offline cracking or relay","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Building SIEM correlation rules for MITRE ATT&CK T1557.001 (LLMNR/NBT-NS Poisoning and SMB Relay)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Responding to PetitPotam, DFSCoerce, or PrinterBug coercion alerts that may precede relay attacks","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"During purple team exercises validating NTLM relay detection and SMB signing enforcement","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Do not use","type":"text","marks":[{"type":"strong"}]},{"text":" without centralized Windows Security Event Log collection, as a substitute for enforcing SMB signing and Extended Protection for Authentication (EPA) which prevent relay attacks at the protocol level, or without an IP-to-hostname inventory for correlation.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Prerequisites","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Windows Advanced Audit Policy configured to capture Event IDs 4624, 4625, 4648, 4776, and 8004","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Centralized log collection via Windows Event Forwarding (WEF) or agent-based shipping to SIEM","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"SIEM platform (Splunk, Elastic, Microsoft Sentinel) with correlation and alerting capability","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"IP address to hostname mapping inventory (DHCP logs, DNS records, or CMDB)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Network monitoring for LLMNR (UDP 5355), NBT-NS (UDP 137), and mDNS (UDP 5353) traffic","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Understanding of MITRE ATT&CK T1557.001 and T1187 (Forced Authentication)","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Workflow","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Step 1: Understand NTLM Relay Attack Flow","type":"text"}]},{"type":"paragraph","content":[{"text":"The NTLM relay attack follows a three-phase pattern: coercion/poisoning, interception, and relay.","type":"text"}]},{"type":"paragraph","content":[{"text":"Phase 1 -- Coercion or Poisoning","type":"text","marks":[{"type":"strong"}]},{"text":": The attacker forces or tricks a victim into initiating NTLM authentication. Methods include LLMNR/NBT-NS poisoning (Responder), PetitPotam (MS-EFSRPC abuse), PrinterBug (SpoolService), and DFSCoerce.","type":"text"}]},{"type":"paragraph","content":[{"text":"Phase 2 -- Interception","type":"text","marks":[{"type":"strong"}]},{"text":": The attacker captures the NTLM Type 1 (Negotiate) and Type 3 (Authenticate) messages from the victim.","type":"text"}]},{"type":"paragraph","content":[{"text":"Phase 3 -- Relay","type":"text","marks":[{"type":"strong"}]},{"text":": The attacker forwards the captured NTLM messages to a target service (SMB, LDAP, HTTP, MSSQL) to authenticate as the victim. This succeeds only when message signing is not enforced.","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"Victim ──NTLM Negotiate──> Attacker ──NTLM Negotiate──> Target\nVictim \u003c──NTLM Challenge── Attacker \u003c──NTLM Challenge── Target\nVictim ──NTLM Authenticate──> Attacker ──NTLM Authenticate──> Target\n ↓\n Attacker authenticated\n as Victim on Target","type":"text"}]},{"type":"paragraph","content":[{"text":"Key Detection Insight","type":"text","marks":[{"type":"strong"}]},{"text":": In a relay attack, Event 4624 on the target will show the victim's username but the attacker's IP address. The WorkstationName field may still reflect the victim's machine. This IP-to-hostname mismatch is the primary detection signal.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Step 2: Event 4624 LogonType 3 Analysis for Relay Detection","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"spl"},"content":[{"text":"# Splunk: Detect IP-to-Hostname Mismatches in Network Logons\n# Core NTLM relay detection -- correlates WorkstationName with IpAddress\n\nindex=wineventlog EventCode=4624 LogonType=3\n AuthenticationPackageName=\"NTLM\" LmPackageName=\"NTLM V2\"\n| where TargetUserName != \"ANONYMOUS LOGON\"\n AND TargetUserName != \"-\"\n AND NOT match(TargetUserName, \".*\\\\$\")\n| eval workstation_lower=lower(WorkstationName)\n| lookup dns_inventory.csv hostname AS workstation_lower OUTPUT expected_ip\n| where isnotnull(expected_ip) AND IpAddress != expected_ip\n| table _time ComputerName TargetUserName WorkstationName IpAddress expected_ip\n LogonProcessName AuthenticationPackageName\n| sort -_time\n| rename ComputerName as TargetHost, IpAddress as ActualSourceIP,\n expected_ip as ExpectedSourceIP","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"spl"},"content":[{"text":"# Splunk: Detect Rapid Multi-Host Authentication (Relay Spraying)\n# Attackers relay captured credentials to multiple targets quickly\n\nindex=wineventlog EventCode=4624 LogonType=3\n AuthenticationPackageName=\"NTLM\"\n| where TargetUserName != \"ANONYMOUS LOGON\"\n AND NOT match(TargetUserName, \".*\\\\$\")\n| bin _time span=2m\n| stats dc(ComputerName) as target_count values(ComputerName) as targets\n values(IpAddress) as source_ips by _time TargetUserName\n| where target_count > 3\n| table _time TargetUserName source_ips target_count targets\n| sort -target_count","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"spl"},"content":[{"text":"# Splunk: Detect NTLM Authentication from Non-Workstation IPs\n# Relay tools often run from Linux attack boxes not in DNS/DHCP inventory\n\nindex=wineventlog EventCode=4624 LogonType=3\n AuthenticationPackageName=\"NTLM\"\n| where TargetUserName != \"ANONYMOUS LOGON\"\n AND NOT match(TargetUserName, \".*\\\\$\")\n| lookup dhcp_leases.csv ip AS IpAddress OUTPUT mac_address hostname\n| where isnull(hostname)\n| stats count dc(ComputerName) as targets_hit values(ComputerName) as target_hosts\n by IpAddress TargetUserName WorkstationName\n| where count > 1\n| table IpAddress TargetUserName WorkstationName targets_hit target_hosts count\n| sort -targets_hit","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"kql"},"content":[{"text":"-- Microsoft Sentinel KQL: NTLM Relay Detection via IP-Hostname Mismatch\n\nlet known_hosts = datatable(WorkstationName:string, ExpectedIP:string)\n[\n // Populate from CMDB or use DeviceNetworkInfo table\n];\nSecurityEvent\n| where EventID == 4624 and LogonType == 3\n| where AuthenticationPackageName == \"NTLM\"\n| where TargetUserName !endswith \"$\"\n| where TargetUserName != \"ANONYMOUS LOGON\"\n| where IpAddress != \"-\" and IpAddress != \"::1\" and IpAddress != \"127.0.0.1\"\n| extend WorkstationClean = toupper(trim_end(@\"\\s+\", WorkstationName))\n| join kind=inner (known_hosts) on WorkstationName\n| where IpAddress != ExpectedIP\n| project TimeGenerated, Computer, TargetUserName, WorkstationName,\n IpAddress, ExpectedIP, LogonProcessName, AuthenticationPackageName,\n LmPackageName\n| sort by TimeGenerated desc","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"kql"},"content":[{"text":"-- Microsoft Sentinel KQL: Rapid NTLM Authentication to Multiple Targets\n\nSecurityEvent\n| where EventID == 4624 and LogonType == 3\n| where AuthenticationPackageName == \"NTLM\"\n| where TargetUserName !endswith \"$\"\n| where TargetUserName != \"ANONYMOUS LOGON\"\n| summarize TargetCount=dcount(Computer),\n Targets=make_set(Computer),\n SourceIPs=make_set(IpAddress),\n AuthCount=count()\n by TargetUserName, bin(TimeGenerated, 2m)\n| where TargetCount > 3\n| project TimeGenerated, TargetUserName, SourceIPs, TargetCount, Targets, AuthCount\n| sort by TargetCount desc","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Step 3: Responder Detection via Network and Event Analysis","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"spl"},"content":[{"text":"# Splunk: Detect Responder LLMNR/NBT-NS Poisoning via Network Logs\n# Responder answers LLMNR (UDP 5355) and NBT-NS (UDP 137) queries\n\nindex=network sourcetype=zeek_dns\n| where query_type IN (\"LLMNR\", \"NBNS\")\n OR id.resp_p IN (5355, 137)\n| stats dc(id.orig_h) as victims count by id.resp_h answers\n| where count > 10\n| rename id.resp_h as responder_ip\n| table responder_ip victims answers count\n| sort -count","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"spl"},"content":[{"text":"# Splunk: Detect LLMNR/NBT-NS Response from Non-DNS Servers\n# Legitimate DNS servers respond to these; Responder impersonates them\n\nindex=network sourcetype=\"bro:dns:json\" OR sourcetype=\"zeek:conn:json\"\n| where id_resp_p=5355 OR id_resp_p=137\n| where NOT cidrmatch(\"10.10.0.0/24\", id_resp_h)\n| stats count dc(id_orig_h) as unique_victims by id_resp_h\n| where unique_victims > 3\n| table id_resp_h unique_victims count\n| rename id_resp_h as suspicious_responder","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"powershell"},"content":[{"text":"# PowerShell: Detect LLMNR and NBT-NS activity on local network\n# Run on a monitoring host to identify Responder-like behavior\n\n# Check if LLMNR is disabled (should be disabled to prevent poisoning)\n$llmnr = Get-ItemProperty -Path \"HKLM:\\SOFTWARE\\Policies\\Microsoft\\Windows NT\\DNSClient\" `\n -Name \"EnableMulticast\" -ErrorAction SilentlyContinue\nWrite-Host \"[*] LLMNR Status: $(if ($llmnr.EnableMulticast -eq 0) { 'DISABLED (Good)' } else { 'ENABLED (Vulnerable to Responder)' })\"\n\n# Check if NBT-NS is disabled\n$adapters = Get-WmiObject -Class Win32_NetworkAdapterConfiguration -Filter \"IPEnabled=True\"\nforeach ($adapter in $adapters) {\n $nbtns = $adapter.TcpipNetbios\n $status = switch ($nbtns) {\n 0 { \"Default (Enabled)\" }\n 1 { \"Enabled\" }\n 2 { \"Disabled (Good)\" }\n }\n Write-Host \"[*] Adapter '$($adapter.Description)' NBT-NS: $status\"\n}\n\n# Query Windows Firewall logs for LLMNR/NBT-NS traffic\nGet-WinEvent -LogName \"Microsoft-Windows-Windows Firewall With Advanced Security/Firewall\" `\n -MaxEvents 1000 -ErrorAction SilentlyContinue |\n Where-Object {\n $_.Message -match \"5355|137\" -and $_.Message -match \"UDP\"\n } |\n Select-Object TimeCreated, @{N='Detail';E={$_.Message.Substring(0,200)}} |\n Format-Table -AutoSize","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"yaml"},"content":[{"text":"# Sigma Rule: Responder LLMNR/NBT-NS Poisoning Detection\ntitle: Potential Responder LLMNR/NBT-NS Poisoning Activity\nid: 7a8b9c0d-e1f2-3a4b-5c6d-7e8f9a0b1c2d\nstatus: stable\ndescription: >\n Detects a single host responding to LLMNR (UDP 5355) or NBT-NS (UDP 137)\n queries from multiple unique sources, indicating possible Responder poisoning.\nreferences:\n - https://www.hackthebox.com/blog/ntlm-relay-attack-detection\n - https://blog.fox-it.com/2017/05/09/relaying-credentials-everywhere-with-ntlmrelayx/\nlogsource:\n category: firewall\ndetection:\n selection:\n dst_port:\n - 5355\n - 137\n action: allow\n condition: selection | count(src_ip) by dst_ip > 5\n timeframe: 5m\nlevel: high\ntags:\n - attack.credential_access\n - attack.t1557.001\nfalsepositives:\n - Legitimate WINS servers or DNS servers responding to broadcast queries\n - Network discovery tools performing name resolution","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Step 4: SMB Signing Enforcement Audit","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"powershell"},"content":[{"text":"# PowerShell: Audit SMB Signing Status Across Domain\n# SMB signing prevents NTLM relay to SMB services\n\n# Check local SMB signing configuration\nWrite-Host \"=== LOCAL SMB SIGNING STATUS ===\"\n$smbServer = Get-SmbServerConfiguration\nWrite-Host \"[*] SMB Server RequireSecuritySignature: $($smbServer.RequireSecuritySignature)\"\nWrite-Host \"[*] SMB Server EnableSecuritySignature: $($smbServer.EnableSecuritySignature)\"\n\n$smbClient = Get-SmbClientConfiguration\nWrite-Host \"[*] SMB Client RequireSecuritySignature: $($smbClient.RequireSecuritySignature)\"\nWrite-Host \"[*] SMB Client EnableSecuritySignature: $($smbClient.EnableSecuritySignature)\"\n\n# Check via registry (works on older systems)\n$serverSigning = Get-ItemProperty -Path \"HKLM:\\SYSTEM\\CurrentControlSet\\Services\\LanManServer\\Parameters\" `\n -Name \"RequireSecuritySignature\" -ErrorAction SilentlyContinue\n$clientSigning = Get-ItemProperty -Path \"HKLM:\\SYSTEM\\CurrentControlSet\\Services\\LanManWorkstation\\Parameters\" `\n -Name \"RequireSecuritySignature\" -ErrorAction SilentlyContinue\n\nWrite-Host \"`n=== REGISTRY VALUES ===\"\nWrite-Host \"[*] Server RequireSecuritySignature: $($serverSigning.RequireSecuritySignature) (1=Required, 0=Not Required)\"\nWrite-Host \"[*] Client RequireSecuritySignature: $($clientSigning.RequireSecuritySignature) (1=Required, 0=Not Required)\"","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"powershell"},"content":[{"text":"# PowerShell: Domain-Wide SMB Signing Audit\n# Scan all domain computers for SMB signing enforcement\n\n$domainComputers = Get-ADComputer -Filter * -Properties OperatingSystem |\n Where-Object { $_.OperatingSystem -like \"*Windows*\" -and $_.Enabled -eq $true } |\n Select-Object -ExpandProperty DNSHostName\n\n$results = @()\nforeach ($computer in $domainComputers) {\n try {\n $session = New-CimSession -ComputerName $computer -ErrorAction Stop\n $smbConfig = Get-SmbServerConfiguration -CimSession $session -ErrorAction Stop\n $results += [PSCustomObject]@{\n Computer = $computer\n RequireSigning = $smbConfig.RequireSecuritySignature\n EnableSigning = $smbConfig.EnableSecuritySignature\n Status = if ($smbConfig.RequireSecuritySignature) { \"ENFORCED\" } else { \"VULNERABLE\" }\n }\n Remove-CimSession $session\n } catch {\n $results += [PSCustomObject]@{\n Computer = $computer\n RequireSigning = \"ERROR\"\n EnableSigning = \"ERROR\"\n Status = \"UNREACHABLE\"\n }\n }\n}\n\n# Display results sorted by vulnerability\n$results | Sort-Object Status | Format-Table -AutoSize\n\n# Export vulnerable hosts\n$vulnerable = $results | Where-Object { $_.Status -eq \"VULNERABLE\" }\nWrite-Host \"`n[!] VULNERABLE HOSTS (SMB Signing Not Required): $($vulnerable.Count)\"\n$vulnerable | Export-Csv -Path \"smb_signing_audit.csv\" -NoTypeInformation","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"powershell"},"content":[{"text":"# PowerShell: Audit LDAP Signing Status on Domain Controllers\n# LDAP signing prevents NTLM relay to LDAP/LDAPS services\n\n# Check LDAP signing requirement on domain controllers\n$dcs = Get-ADDomainController -Filter * | Select-Object -ExpandProperty HostName\n\nforeach ($dc in $dcs) {\n # Check LDAP server signing requirement\n $ldapSigning = Invoke-Command -ComputerName $dc -ScriptBlock {\n $regPath = \"HKLM:\\SYSTEM\\CurrentControlSet\\Services\\NTDS\\Parameters\"\n $value = Get-ItemProperty -Path $regPath -Name \"LDAPServerIntegrity\" -ErrorAction SilentlyContinue\n return $value.LDAPServerIntegrity\n } -ErrorAction SilentlyContinue\n\n $status = switch ($ldapSigning) {\n 0 { \"NONE (Vulnerable)\" }\n 1 { \"Negotiate Signing (Default - Vulnerable to relay)\" }\n 2 { \"Require Signing (Secure)\" }\n default { \"Unknown/Error\" }\n }\n Write-Host \"[*] $dc LDAP Signing: $status\"\n\n # Check LDAP channel binding\n $channelBinding = Invoke-Command -ComputerName $dc -ScriptBlock {\n $regPath = \"HKLM:\\SYSTEM\\CurrentControlSet\\Services\\NTDS\\Parameters\"\n $value = Get-ItemProperty -Path $regPath -Name \"LdapEnforceChannelBinding\" -ErrorAction SilentlyContinue\n return $value.LdapEnforceChannelBinding\n } -ErrorAction SilentlyContinue\n\n $cbStatus = switch ($channelBinding) {\n 0 { \"Disabled (Vulnerable)\" }\n 1 { \"When Supported\" }\n 2 { \"Always Required (Secure)\" }\n default { \"Not Configured (Vulnerable)\" }\n }\n Write-Host \"[*] $dc LDAP Channel Binding: $cbStatus\"\n}","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"spl"},"content":[{"text":"# Splunk: Monitor for SMB sessions without signing\n# Requires Zeek SMB logging or packet capture analysis\n\nindex=network sourcetype=\"zeek:smb_mapping:json\" OR sourcetype=\"bro:smb_mapping:json\"\n| where NOT security_mode=\"signing_required\"\n| stats count dc(id_orig_h) as unique_clients by id_resp_h security_mode\n| sort -unique_clients\n| rename id_resp_h as smb_server\n| table smb_server security_mode unique_clients count","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Step 5: NTLM Downgrade Detection","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"spl"},"content":[{"text":"# Splunk: Detect NTLMv1 Authentication (Downgrade from NTLMv2)\n# NTLMv1 is weaker and easier to relay/crack -- should not be in use\n\nindex=wineventlog EventCode=4624 LogonType=3\n LmPackageName=\"NTLM V1\"\n| where TargetUserName != \"ANONYMOUS LOGON\"\n AND NOT match(TargetUserName, \".*\\\\$\")\n| stats count values(ComputerName) as targets\n values(IpAddress) as source_ips\n by TargetUserName LmPackageName\n| table TargetUserName LmPackageName source_ips targets count\n| sort -count","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"spl"},"content":[{"text":"# Splunk: Detect NTLM Downgrade Attack Pattern\n# NTLMv1 appearing after a period of only NTLMv2 suggests active downgrade\n\nindex=wineventlog EventCode=4624 LogonType=3\n AuthenticationPackageName=\"NTLM\"\n| where TargetUserName != \"ANONYMOUS LOGON\"\n| bin _time span=1h\n| stats count(eval(LmPackageName=\"NTLM V1\")) as ntlmv1_count\n count(eval(LmPackageName=\"NTLM V2\")) as ntlmv2_count\n by _time\n| where ntlmv1_count > 0\n| eval ntlmv1_ratio = round(ntlmv1_count / (ntlmv1_count + ntlmv2_count) * 100, 2)\n| table _time ntlmv1_count ntlmv2_count ntlmv1_ratio\n| sort -_time","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"kql"},"content":[{"text":"-- Microsoft Sentinel KQL: NTLMv1 Downgrade Detection\n\nSecurityEvent\n| where EventID == 4624 and LogonType == 3\n| where AuthenticationPackageName == \"NTLM\"\n| where LmPackageName == \"NTLM V1\"\n| where TargetUserName !endswith \"$\"\n| where TargetUserName != \"ANONYMOUS LOGON\"\n| project TimeGenerated, Computer, TargetUserName, WorkstationName,\n IpAddress, LmPackageName, LogonProcessName\n| sort by TimeGenerated desc","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"powershell"},"content":[{"text":"# PowerShell: Detect NTLMv1 Authentication Events on Local System\n\n$ntlmv1Events = Get-WinEvent -LogName Security -FilterXPath @\"\n*[System[(EventID=4624)]]\n and\n*[EventData[Data[@Name='LmPackageName']='NTLM V1']]\n\"@ -MaxEvents 500 -ErrorAction SilentlyContinue\n\nif ($ntlmv1Events.Count -gt 0) {\n Write-Host \"[!] WARNING: $($ntlmv1Events.Count) NTLMv1 authentication events detected!\" -ForegroundColor Red\n $ntlmv1Events | ForEach-Object {\n $xml = [xml]$_.ToXml()\n $eventData = $xml.Event.EventData.Data\n [PSCustomObject]@{\n Time = $_.TimeCreated\n TargetUser = ($eventData | Where-Object { $_.Name -eq \"TargetUserName\" }).'#text'\n Workstation = ($eventData | Where-Object { $_.Name -eq \"WorkstationName\" }).'#text'\n SourceIP = ($eventData | Where-Object { $_.Name -eq \"IpAddress\" }).'#text'\n LmPackage = ($eventData | Where-Object { $_.Name -eq \"LmPackageName\" }).'#text'\n }\n } | Format-Table -AutoSize\n} else {\n Write-Host \"[+] No NTLMv1 authentication events found (Good)\" -ForegroundColor Green\n}\n\n# Audit GPO settings for NTLM restriction\nWrite-Host \"`n=== NTLM RESTRICTION POLICY ===\"\n$ntlmPolicy = Get-ItemProperty -Path \"HKLM:\\SYSTEM\\CurrentControlSet\\Control\\Lsa\" `\n -Name \"LmCompatibilityLevel\" -ErrorAction SilentlyContinue\n\n$level = switch ($ntlmPolicy.LmCompatibilityLevel) {\n 0 { \"Send LM & NTLM responses (Most Vulnerable)\" }\n 1 { \"Send LM & NTLM - use NTLMv2 session security if negotiated\" }\n 2 { \"Send NTLM response only\" }\n 3 { \"Send NTLMv2 response only (Recommended minimum)\" }\n 4 { \"Send NTLMv2 response only, refuse LM\" }\n 5 { \"Send NTLMv2 response only, refuse LM & NTLM (Most Secure)\" }\n default { \"Not configured (defaults to 3 on modern Windows)\" }\n}\nWrite-Host \"[*] LmCompatibilityLevel: $($ntlmPolicy.LmCompatibilityLevel) - $level\"","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Step 6: NTLM Audit and Restriction Policy Configuration","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"powershell"},"content":[{"text":"# PowerShell: Enable NTLM Auditing via Group Policy Registry Settings\n# Must be applied via GPO for domain-wide coverage\n\n# Audit all NTLM authentication in this domain\n# GPO: Computer Configuration > Policies > Windows Settings > Security Settings >\n# Local Policies > Security Options >\n# Network Security: Restrict NTLM: Audit NTLM authentication in this domain = Enable all\n\n# Registry equivalent (apply via GPO preferences or startup script)\n# Domain Controller setting:\n# Set-ItemProperty -Path \"HKLM:\\SYSTEM\\CurrentControlSet\\Services\\Netlogon\\Parameters\" `\n# -Name \"AuditNTLMInDomain\" -Value 7 -Type DWord\n\n# Audit incoming NTLM traffic on all servers:\n# Set-ItemProperty -Path \"HKLM:\\SYSTEM\\CurrentControlSet\\Control\\Lsa\\MSV1_0\" `\n# -Name \"AuditReceivingNTLMTraffic\" -Value 2 -Type DWord\n\n# After enabling auditing, NTLM events appear in:\n# Applications and Services Logs > Microsoft > Windows > NTLM > Operational\n\n# Query NTLM operational log for audit events\nGet-WinEvent -LogName \"Microsoft-Windows-NTLM/Operational\" -MaxEvents 200 -ErrorAction SilentlyContinue |\n Where-Object { $_.Id -in @(8001, 8002, 8003, 8004) } |\n Select-Object TimeCreated, Id,\n @{N='EventType'; E={\n switch ($_.Id) {\n 8001 { \"NTLM client blocked audit\" }\n 8002 { \"NTLM server blocked audit\" }\n 8003 { \"NTLM server blocked in domain\" }\n 8004 { \"NTLM authentication to DC audit\" }\n }\n }},\n @{N='Detail'; E={$_.Message.Substring(0, [Math]::Min(300, $_.Message.Length))}} |\n Format-Table -AutoSize","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"spl"},"content":[{"text":"# Splunk: Monitor NTLM Audit Events (Event ID 8004)\n# Shows all NTLM authentications passing through domain controllers\n\nindex=wineventlog source=\"WinEventLog:Microsoft-Windows-NTLM/Operational\"\n EventCode=8004\n| rex field=Message \"Calling client name:\\s+(?\u003cclient_name>[^\\r\\n]+)\"\n| rex field=Message \"Calling client IP:\\s+(?\u003cclient_ip>[^\\r\\n]+)\"\n| rex field=Message \"Server name:\\s+(?\u003cserver_name>[^\\r\\n]+)\"\n| stats count dc(server_name) as unique_servers by client_name client_ip\n| sort -count\n| table client_name client_ip unique_servers count","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Step 7: PetitPotam and Coercion Attack Detection","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"spl"},"content":[{"text":"# Splunk: Detect PetitPotam / EFSCoerce Attack\n# Monitor for machine account NTLM authentications relayed to other services\n\nindex=wineventlog EventCode=4624 LogonType=3\n AuthenticationPackageName=\"NTLM\"\n TargetUserName=\"*$\"\n| where match(TargetUserName, \"^[A-Z0-9\\\\-]+\\\\$\")\n| eval is_dc = if(match(TargetUserName, \"(DC|DCSERVER|DOMCTRL)\"), \"Yes\", \"No\")\n| where IpAddress != \"127.0.0.1\" AND IpAddress != \"::1\"\n| stats count values(ComputerName) as target_hosts\n values(IpAddress) as source_ips by TargetUserName\n| where count > 2 OR mvcount(source_ips) > 1\n| table TargetUserName source_ips target_hosts count\n| sort -count","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"kql"},"content":[{"text":"-- Microsoft Sentinel KQL: PetitPotam / Coercion Attack Detection\n-- Detects domain controller machine account authenticating from unexpected IPs\n\nlet dc_accounts = SecurityEvent\n| where EventID == 4624 and LogonType == 3\n| where TargetUserName endswith \"$\"\n| where Computer startswith \"DC\"\n| distinct TargetUserName;\n\nSecurityEvent\n| where EventID == 4624 and LogonType == 3\n| where AuthenticationPackageName == \"NTLM\"\n| where TargetUserName in (dc_accounts)\n| where IpAddress != \"127.0.0.1\" and IpAddress != \"::1\"\n| extend SourceHostExpected = iff(\n Computer == replace_string(TargetUserName, \"$\", \"\"), true, false)\n| where SourceHostExpected == false\n| project TimeGenerated, Computer, TargetUserName, IpAddress,\n WorkstationName, LogonProcessName, AuthenticationPackageName\n| sort by TimeGenerated desc","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"yaml"},"content":[{"text":"# Sigma Rule: NTLM Relay - Computer Account Authentication from Unexpected Source\ntitle: Potential NTLM Relay of Computer Account Credentials\nid: 5e6f7a8b-9c0d-1e2f-3a4b-5c6d7e8f9a0b\nstatus: stable\ndescription: >\n Detects a computer account (ending in $) authenticating via NTLM LogonType 3\n where the source IP does not match the computer's known IP, indicating possible\n NTLM relay of coerced machine authentication (PetitPotam, DFSCoerce, PrinterBug).\nreferences:\n - https://www.crowdstrike.com/en-us/blog/how-to-detect-domain-controller-account-relay-attacks-with-crowdstrike-identity-protection/\n - https://www.fox-it.com/nl-en/research-blog/detecting-and-hunting-for-the-petitpotam-ntlm-relay-attack/\n - https://www.nccgroup.com/research-blog/detecting-and-hunting-for-the-petitpotam-ntlm-relay-attack/\nlogsource:\n product: windows\n service: security\ndetection:\n selection:\n EventID: 4624\n LogonType: 3\n AuthenticationPackageName: NTLM\n TargetUserName|endswith: '

Detecting NTLM Relay with Event Correlation Authorized Testing Disclaimer : The offensive techniques and attack simulations described in this skill are intended exclusively for authorized penetration testing, red team engagements, purple team exercises, and security research conducted with explicit written permission from the system owner. Unauthorized use of these techniques against systems you do not own or have permission to test is illegal and unethical. Always operate within the scope of your engagement and comply with applicable laws and regulations. Overview NTLM relay attacks intercep…

\n filter_localhost:\n IpAddress:\n - '127.0.0.1'\n - '::1'\n - '-'\n condition: selection and not filter_localhost\nlevel: high\ntags:\n - attack.credential_access\n - attack.t1557.001\n - attack.t1187\nfalsepositives:\n - Legitimate NTLM authentication from machine accounts during failover\n - Cluster service machine account authentication","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Step 8: Build Comprehensive Correlation Dashboard","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"spl"},"content":[{"text":"# Splunk: NTLM Relay Detection Dashboard -- Combined Correlation Query\n\n# Panel 1: IP-Hostname Mismatches (Core Relay Indicator)\nindex=wineventlog EventCode=4624 LogonType=3 AuthenticationPackageName=\"NTLM\"\n| where TargetUserName != \"ANONYMOUS LOGON\" AND NOT match(TargetUserName, \".*\\\\$\")\n| eval mismatch=if(lower(WorkstationName) != lower(mvindex(split(IpAddress, \".\"), 0)),\n \"POSSIBLE_MISMATCH\", \"OK\")\n| where mismatch=\"POSSIBLE_MISMATCH\"\n| stats count by TargetUserName WorkstationName IpAddress ComputerName\n\n# Panel 2: NTLMv1 Downgrade Events\nindex=wineventlog EventCode=4624 LmPackageName=\"NTLM V1\"\n| timechart span=1h count by ComputerName\n\n# Panel 3: Machine Account Relay (PetitPotam Indicator)\nindex=wineventlog EventCode=4624 LogonType=3 AuthenticationPackageName=\"NTLM\"\n TargetUserName=\"*$\"\n| stats count values(IpAddress) as relay_sources by TargetUserName ComputerName\n\n# Panel 4: NTLM Authentication Volume Anomaly\nindex=wineventlog EventCode=4624 LogonType=3 AuthenticationPackageName=\"NTLM\"\n| timechart span=15m count\n| streamstats window=20 avg(count) as avg_count stdev(count) as stdev_count\n| eval upper_bound=avg_count + (3 * stdev_count)\n| where count > upper_bound\n\n# Panel 5: SMB Signing Status (from audit results)\n| inputlookup smb_signing_audit.csv\n| stats count by Status\n| table Status count","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Key Concepts","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Term","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Definition","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"NTLM Relay (T1557.001)","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Attack that intercepts NTLM authentication messages and forwards them to a target service, authenticating as the victim without knowing their password","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Event 4624 LogonType 3","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Windows Security Event for successful network logon -- the primary event generated on relay targets; source IP field reveals the relay attacker's address","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"IP-Hostname Mismatch","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"When Event 4624 WorkstationName field does not correspond to the IpAddress field, indicating the authentication was relayed through a third party","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Responder","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Attack tool that poisons LLMNR (UDP 5355), NBT-NS (UDP 137), and mDNS (UDP 5353) responses to capture NTLM authentication from victims on the local network","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"ntlmrelayx","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Fox-IT/Impacket tool that relays captured NTLM authentication to SMB, LDAP, HTTP, MSSQL, and other protocols to gain unauthorized access","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"SMB Signing","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Cryptographic signing of SMB packets that prevents relay attacks against SMB services; must be set to \"Required\" (not just \"Enabled\") for protection","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"LDAP Signing","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Cryptographic signing of LDAP operations that prevents relay attacks against LDAP services on domain controllers; controlled by LDAPServerIntegrity registry value","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"LDAP Channel Binding","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Extended Protection for Authentication (EPA) that binds the NTLM authentication to the TLS channel, preventing relay to LDAPS","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"NTLMv1 Downgrade","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Attack forcing authentication from NTLMv2 to the weaker NTLMv1 protocol, which is easier to crack offline and has weaker relay protections","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"PetitPotam","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Coercion technique abusing MS-EFSRPC to force a domain controller to authenticate to an attacker-controlled host, enabling relay to AD CS or LDAP","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"LmCompatibilityLevel","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Registry setting controlling which NTLM version is used; value of 5 (Send NTLMv2 only, refuse LM and NTLM) provides strongest protection","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Event 8004","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"NTLM operational log event on domain controllers showing all NTLM authentication pass-through, critical for auditing NTLM usage before restriction","type":"text"}]}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Tools & Systems","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Tool","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Purpose","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Splunk / Elastic SIEM","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Log aggregation and correlation for Event 4624 analysis, IP-hostname mismatch detection, and NTLM downgrade monitoring","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Microsoft Sentinel","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Cloud SIEM with KQL queries for NTLM relay detection and built-in analytics rules for PetitPotam","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"CrowdStrike Falcon Identity Protection","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Detects NTLM relay attacks against domain controller accounts regardless of coercion method used","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Responder","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"LLMNR/NBT-NS/mDNS poisoning tool used by attackers -- understanding its behavior is essential for detection","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"ntlmrelayx (Impacket)","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Multi-protocol NTLM relay tool developed by Fox-IT -- used in testing and by adversaries","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"PingCastle","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Active Directory security assessment tool that audits SMB signing, LDAP signing, and NTLM configuration","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Zeek","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Network security monitor for capturing SMB signing negotiation, LLMNR traffic, and DCE-RPC activity","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Sigma","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Vendor-agnostic detection rule format for portable NTLM relay detection rules","type":"text"}]}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Common Scenarios","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Scenario 1: Responder Poisoning with NTLM Relay to File Server","type":"text"}]},{"type":"paragraph","content":[{"text":"Context","type":"text","marks":[{"type":"strong"}]},{"text":": A SOC analyst observes multiple Event 4624 LogonType 3 entries on a file server (10.10.20.100) where the WorkstationName field shows different workstation names but the IpAddress field consistently shows 10.10.5.50, a host not in the IT asset inventory.","type":"text"}]},{"type":"paragraph","content":[{"text":"Approach","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Query Event 4624 on 10.10.20.100 filtered for IpAddress=10.10.5.50: find 15 successful NTLM logons in 30 minutes from 8 different user accounts","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Cross-reference 10.10.5.50 with DHCP logs and DNS: host is not a registered domain member, MAC address shows a Linux-based NIC","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Query Zeek network logs for 10.10.5.50: identify LLMNR responses (UDP 5355) to multiple workstations and SMB connections to 10.10.20.100","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Confirm IP-hostname mismatch: WorkstationName values (WS-FINANCE01, WS-HR03, etc.) all resolve to different IPs in DNS, not 10.10.5.50","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Check SMB signing on 10.10.20.100: RequireSecuritySignature is False, enabling the relay attack","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Contain: block 10.10.5.50 at the switch, force password reset for all 8 affected accounts, enable SMB signing on the file server","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Remediate: disable LLMNR and NBT-NS via GPO, enforce SMB signing domain-wide","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Pitfalls","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Dismissing the multiple logons as normal network activity without checking the IP-hostname correlation","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Not checking SMB signing status on the target server to understand why the relay succeeded","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Only resetting the password for one user instead of all accounts that were relayed","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Scenario 2: PetitPotam Relay to AD Certificate Services","type":"text"}]},{"type":"paragraph","content":[{"text":"Context","type":"text","marks":[{"type":"strong"}]},{"text":": During a threat hunt, an analyst finds Event 4624 LogonType 3 on the AD CS server (ADCS01) showing the domain controller machine account (DC01$) authenticating via NTLM from IP 10.10.5.50, which is not the DC's IP address (10.10.1.10).","type":"text"}]},{"type":"paragraph","content":[{"text":"Approach","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Confirm the anomaly: DC01$ should only authenticate from 10.10.1.10, but Event 4624 shows authentication from 10.10.5.50 via NTLM (not Kerberos)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Check for certificate enrollment: query AD CS logs for certificate requests from DC01$ around the same timestamp -- find a certificate issued for DC01$","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Identify the attack: PetitPotam coerced DC01 to authenticate to 10.10.5.50, which relayed the authentication to ADCS01 to request a certificate for DC01$","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Assess impact: with a DC certificate, the attacker can authenticate as DC01$ and perform DCSync to extract all domain credentials","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Revoke the fraudulently issued certificate immediately","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Check for DCSync activity: query Event 4662 for directory replication from non-DC sources","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Contain: isolate 10.10.5.50, revoke certificate, patch EFS (MS-EFSRPC), enforce EPA on AD CS, require LDAP signing on all DCs","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Pitfalls","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Not recognizing that machine account NTLM authentication from an unexpected IP is a critical indicator of coercion + relay","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Failing to check AD CS for fraudulent certificate issuance, which represents the actual objective of the attack","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Not auditing LDAP signing and EPA on AD CS servers, which would have prevented the relay","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Output Format","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"Hunt ID: TH-NTLM-RELAY-[DATE]-[SEQ]\nAlert Severity: Critical\nMITRE Technique: T1557.001 (LLMNR/NBT-NS Poisoning and SMB Relay)\n\nRelay Indicators:\n Victim Account: [Domain\\Username or Machine$]\n WorkstationName: [Victim hostname from Event 4624]\n Expected Source IP: [IP matching WorkstationName in DNS/DHCP]\n Actual Source IP: [Attacker/relay IP from Event 4624 IpAddress field]\n Target Host: [Server receiving the relayed authentication]\n\nAuthentication Details:\n Event ID: 4624\n LogonType: 3 (Network)\n AuthenticationPackage: NTLM\n LmPackageName: [NTLM V1 or NTLM V2]\n LogonProcess: [NtLmSsp]\n Timestamp: [Event time]\n\nSigning Status:\n Target SMB Signing: [Required/Not Required]\n Target LDAP Signing: [Required/Not Required]\n LDAP Channel Binding: [Required/Not Required]\n\nPoisoning Evidence:\n LLMNR Activity: [Detected/Not Detected from relay IP]\n NBT-NS Activity: [Detected/Not Detected from relay IP]\n Coercion Method: [PetitPotam/DFSCoerce/PrinterBug/Unknown]\n\nRisk Assessment: [Critical - relay from DC / High - relay from user account]\nRecommended Actions:\n - Immediate: [Block relay IP, reset affected credentials]\n - Short-term: [Enable SMB/LDAP signing, disable LLMNR/NBT-NS]\n - Long-term: [Migrate to Kerberos, enforce EPA, restrict NTLM via GPO]","type":"text"}]},{"type":"hr","attrs":{"markup":"---"}}]},"metadata":{"date":"2026-06-05","name":"detecting-ntlm-relay-with-event-correlation","tags":["threat-hunting","NTLM-relay","event-correlation","T1557.001","Event-4624","Responder","SMB-signing","LDAP-signing","NTLM-downgrade","PetitPotam","Active-Directory"],"author":"@skillopedia","domain":"cybersecurity","source":{"stars":13207,"repo_name":"anthropic-cybersecurity-skills","origin_url":"https://github.com/mukul975/anthropic-cybersecurity-skills/blob/HEAD/skills/detecting-ntlm-relay-with-event-correlation/SKILL.md","repo_owner":"mukul975","body_sha256":"9b50f03b8bb7e49547ee4277f91ca1ef4381f884327e62ded8254b2349845fe0","cluster_key":"3c3ff38081d638ff938d6b8b544bfeb594a8b28eb503cc635fcce6ba558b156c","clean_bundle":{"format":"clean-skill-bundle-v1","source":"mukul975/anthropic-cybersecurity-skills/skills/detecting-ntlm-relay-with-event-correlation/SKILL.md","attachments":[{"id":"db208d30-40af-5f84-a23c-0fda3e1de3cd","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/db208d30-40af-5f84-a23c-0fda3e1de3cd/attachment.md","path":"references/api-reference.md","size":7407,"sha256":"f851494565bc4d5ab6d87272bbcab6402df737f6bf89db54fe8c480aeaa72ce0","contentType":"text/markdown; charset=utf-8"},{"id":"130acb09-f1bd-5c8d-95d9-72c3d3ea60ef","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/130acb09-f1bd-5c8d-95d9-72c3d3ea60ef/attachment.py","path":"scripts/agent.py","size":14721,"sha256":"f79da93faf552dceb831fd77114d7dd8891943296c67a89bdcc56834c9daace5","contentType":"text/x-python; charset=utf-8"},{"id":"4a559400-3c4b-54b5-8cdd-79690df3ffa0","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/4a559400-3c4b-54b5-8cdd-79690df3ffa0/attachment.ps1","path":"scripts/audit_smb_signing.ps1","size":13912,"sha256":"5c944978c064f6c21bbaa3a88bbf95c051278e38cd5587456b7ebc4ac4615d1b","contentType":"text/plain; charset=utf-8"},{"id":"e689d0a3-0096-563b-bf9b-7f2d08e6f475","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/e689d0a3-0096-563b-bf9b-7f2d08e6f475/attachment.py","path":"scripts/detect_ntlm_relay.py","size":23511,"sha256":"81e7d6405975a8b8afe76e229658aac95fdf72f3d201138c75dbdf21a50c4781","contentType":"text/x-python; charset=utf-8"}],"bundle_sha256":"12db5a08e29bb45ee9323ccb3e0d7f0aa05e31a9decb98d9ab409fca30a37fec","attachment_count":4,"text_attachments":4,"attachment_storage":"skillopedia-attachments-v1","binary_attachments":0,"excluded_attachments":[]},"cluster_size":1,"skill_md_path":"skills/detecting-ntlm-relay-with-event-correlation/SKILL.md","import_metadata":{"date":"2026-06-05","author":"@skillopedia","version":"v1","category":"security","category_label":"Security"},"exact_dupes_collapsed_into_this":0},"license":"Apache-2.0","version":"v1","category":"security","nist_csf":["DE.CM-01","DE.AE-02","DE.AE-07","ID.RA-05"],"subdomain":"threat-hunting","import_tag":"clean-skills-v1","description":"Detect NTLM relay attacks through Windows Security Event correlation by analyzing Event 4624 LogonType 3 for IP-to-hostname mismatches, identifying Responder/LLMNR poisoning artifacts, auditing SMB and LDAP signing enforcement across the domain, and detecting NTLM downgrade attacks from NTLMv2 to NTLMv1 using event log analysis.\n","nist_ai_rmf":["MEASURE-2.7","MEASURE-2.5","GOVERN-6.1","MAP-5.1"],"atlas_techniques":["AML.T0051","AML.T0054","AML.T0056","AML.T0020"],"d3fend_techniques":["Application Protocol Command Analysis","Network Isolation","Network Traffic Analysis","Client-server Payload Profiling","Network Traffic Community Deviation"]}},"renderedAt":1782981680999}

Detecting NTLM Relay with Event Correlation Authorized Testing Disclaimer : The offensive techniques and attack simulations described in this skill are intended exclusively for authorized penetration testing, red team engagements, purple team exercises, and security research conducted with explicit written permission from the system owner. Unauthorized use of these techniques against systems you do not own or have permission to test is illegal and unethical. Always operate within the scope of your engagement and comply with applicable laws and regulations. Overview NTLM relay attacks intercep…