Skip to content

Commit e434ec0

Browse files
authored
Merge pull request #905 from Mauriceter/CVE-2025-33073
2 parents 8348fbd + 3efb829 commit e434ec0

File tree

2 files changed

+97
-0
lines changed

2 files changed

+97
-0
lines changed

nxc/modules/ntlm_reflection.py

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import time
2+
from impacket.dcerpc.v5 import transport, rrp
3+
from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_GSS_NEGOTIATE
4+
from impacket.smbconnection import SessionError
5+
from nxc.helpers.misc import CATEGORY
6+
7+
8+
class NXCModule:
9+
# https://www.synacktiv.com/en/publications/ntlm-reflection-is-dead-long-live-ntlm-reflection-an-in-depth-analysis-of-cve-2025
10+
name = "ntlm_reflection"
11+
description = "Attempt to check if the OS is vulnerable to CVE-2025-33073 (NTLM Reflection attack)"
12+
supported_protocols = ["smb"]
13+
opsec_safe = True
14+
multiple_hosts = True
15+
category = CATEGORY.ENUMERATION
16+
17+
# Reference table from MSRC report
18+
# https://msrc.microsoft.com/update-guide/fr-FRS/vulnerability/CVE-2025-33073
19+
MSRC_PATCHES = { # key = (major, minor, build), value = minimum patched UBR
20+
(6, 0, 6003): 23351, # Windows Server 2008 SP2
21+
(6, 1, 7601): 27769, # Windows Server 2008 R2 SP1
22+
(6, 2, 9200): 25522, # Windows Server 2012
23+
(6, 3, 9600): 22620, # Windows Server 2012 R2
24+
(10, 0, 14393): 8148, # Windows Server 2016
25+
(10, 0, 17763): 7434, # Windows Server 2019 / Win10 1809
26+
(10, 0, 20348): 3807, # Windows Server 2022
27+
(10, 0, 19044): 5965, # Windows 10 21H2
28+
(10, 0, 22621): 5472, # Windows 11 22H2
29+
}
30+
31+
def __init__(self, context=None, module_options=None):
32+
self.context = context
33+
self.module_options = module_options
34+
35+
def options(self, context, module_options):
36+
"""No options available"""
37+
38+
def is_vulnerable(self, major, minor, build, ubr):
39+
key = (major, minor, build)
40+
min_patched_ubr = self.MSRC_PATCHES.get(key)
41+
if min_patched_ubr is None:
42+
return None # Unknown product
43+
if ubr is None:
44+
return None
45+
return ubr < min_patched_ubr
46+
47+
def on_login(self, context, connection):
48+
self.context = context
49+
self.connection = connection
50+
if not connection.conn.isSigningRequired(): # Not vulnerable if SMB signing is enabled
51+
self.trigger_winreg(connection.conn, context)
52+
rpc = transport.DCERPCTransportFactory(r"ncacn_np:445[\pipe\winreg]")
53+
rpc.set_smb_connection(connection.conn)
54+
if connection.kerberos:
55+
rpc.set_kerberos(connection.kerberos, kdcHost=connection.kdcHost)
56+
dce = rpc.get_dce_rpc()
57+
if connection.kerberos:
58+
dce.set_auth_type(RPC_C_AUTHN_GSS_NEGOTIATE)
59+
try:
60+
dce.connect()
61+
dce.bind(rrp.MSRPC_UUID_RRP)
62+
# Reading UBR from registry
63+
hRootKey = rrp.hOpenLocalMachine(dce)["phKey"]
64+
hKey = rrp.hBaseRegOpenKey(dce, hRootKey, "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion")["phkResult"]
65+
ubr = rrp.hBaseRegQueryValue(dce, hKey, "UBR")[1]
66+
version_str = f"{connection.server_os_major}.{connection.server_os_minor}.{connection.server_os_build}.{ubr}" if ubr else None
67+
dce.disconnect()
68+
if not version_str:
69+
self.context.log.info("Could not determine OS version from registry")
70+
return
71+
vuln = self.is_vulnerable(connection.server_os_major, connection.server_os_minor, connection.server_os_build, ubr)
72+
if vuln:
73+
context.log.highlight(f"VULNERABLE to {self.name}! {connection.server_os} ({version_str})")
74+
except SessionError as e:
75+
if "STATUS_OBJECT_NAME_NOT_FOUND" in str(e):
76+
self.context.log.info(f"RemoteRegistry is probably deactivated: {e}")
77+
else:
78+
self.context.log.debug(f"Unexpected error: {e}")
79+
80+
def trigger_winreg(self, connection, context):
81+
# Original idea from https://twitter.com/splinter_code/status/1715876413474025704
82+
# Basically triggers the RemoteRegistry to start without admin privs
83+
tid = connection.connectTree("IPC$")
84+
try:
85+
connection.openFile(
86+
tid,
87+
r"\winreg",
88+
0x12019F,
89+
creationOption=0x40,
90+
fileAttributes=0x80,
91+
)
92+
except SessionError as e:
93+
# STATUS_PIPE_NOT_AVAILABLE error is expected
94+
context.log.debug(str(e))
95+
# Give remote registry time to start
96+
time.sleep(1)

tests/e2e_commands.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M ioxidres
9595
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M security-questions
9696
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M remove-mic
9797
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M backup_operator
98+
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M ntlm_reflection
9899
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M ntds-dump-raw
99100
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M ntds-dump-raw -o TARGET=SAM,LSA,NTDS
100101
# currently hanging indefinitely - TODO: look into this

0 commit comments

Comments
 (0)