1+ # Original Author:
2+ # Dirk-jan Mollema (@_dirkjan)
3+ # dlive (@D1iv3)
4+ #
5+ # Refernece:
6+ # - https://dirkjanm.io/exploiting-CVE-2019-1040-relay-vulnerabilities-for-rce-and-domain-admin/
7+ # - https://github.com/fox-it/cve-2019-1040-scanner
8+ # - https://github.com/Dliv3/cve-2019-1040-scanner
9+ #
10+ # Modify by:
11+ # XiaoliChan (@Memory_before)
12+
13+ import calendar
14+ import struct
15+ import time
16+ import random
17+ import string
18+
19+ from impacket import ntlm
20+ from impacket import nt_errors
21+ from impacket .smbconnection import SessionError
22+
23+
24+ class NXCModule :
25+ name = "remove-mic"
26+ description = "Check if host vulnerable to CVE-2019-1040"
27+ supported_protocols = ["smb" ]
28+ opsec_safe = True
29+ multiple_hosts = False
30+
31+ def __init__ (self , context = None , module_options = None ):
32+ self .context = context
33+ self .module_options = module_options
34+ self .action = None
35+
36+ def options (self , context , module_options ):
37+ """PORT Port to check (defaults to 445)"""
38+ self .port = 445
39+ if "PORT" in module_options :
40+ self .port = int (module_options ["PORT" ])
41+
42+ def on_login (self , context , connection ):
43+ ntlm .computeResponseNTLMv2 = Modify_Func .mod_computeResponseNTLMv2
44+ ntlm .getNTLMSSPType3 = Modify_Func .mod_getNTLMSSPType3
45+ try :
46+ connection .conn .reconnect ()
47+ except SessionError as e :
48+ if e .getErrorCode () == nt_errors .STATUS_INVALID_PARAMETER :
49+ context .log .info ("Target is not vulnerable to CVE-2019-1040 (authentication was rejected)" )
50+ else :
51+ context .log .info ("Unexpected Exception while authentication" )
52+ else :
53+ context .log .highlight ("Potentially vulnerable to CVE-2019-1040, next step: https://dirkjanm.io/exploiting-CVE-2019-1040-relay-vulnerabilities-for-rce-and-domain-admin/" )
54+
55+ class Modify_Func :
56+ # Slightly modified version of impackets computeResponseNTLMv2
57+ def mod_computeResponseNTLMv2 (flags , serverChallenge , clientChallenge , serverName , domain , user , password , lmhash = "" , nthash = "" ,
58+ use_ntlmv2 = ntlm .USE_NTLMv2 , channel_binding_value = b"" ):
59+
60+ responseServerVersion = b"\x01 "
61+ hiResponseServerVersion = b"\x01 "
62+ responseKeyNT = ntlm .NTOWFv2 (user , password , domain , nthash )
63+
64+ av_pairs = ntlm .AV_PAIRS (serverName )
65+ # In order to support SPN target name validation, we have to add this to the serverName av_pairs. Otherwise we will
66+ # get access denied
67+ # This is set at Local Security Policy -> Local Policies -> Security Options -> Server SPN target name validation
68+ # level
69+ av_pairs [ntlm .NTLMSSP_AV_TARGET_NAME ] = "cifs/" .encode ("utf-16le" ) + av_pairs [ntlm .NTLMSSP_AV_HOSTNAME ][1 ]
70+ if av_pairs [ntlm .NTLMSSP_AV_TIME ] is not None :
71+ aTime = av_pairs [ntlm .NTLMSSP_AV_TIME ][1 ]
72+ else :
73+ aTime = struct .pack ("<q" , (116444736000000000 + calendar .timegm (time .gmtime ()) * 10000000 ))
74+ av_pairs [ntlm .NTLMSSP_AV_TIME ] = aTime
75+ av_pairs [ntlm .NTLMSSP_AV_FLAGS ] = b"\x02 " + b"\x00 " * 3
76+ serverName = av_pairs .getData ()
77+
78+ if len (channel_binding_value ) > 0 :
79+ av_pairs [ntlm .NTLMSSP_AV_CHANNEL_BINDINGS ] = channel_binding_value
80+
81+ # Format according to:
82+ # https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/aee311d6-21a7-4470-92a5-c4ecb022a87b
83+ temp = responseServerVersion # RespType 1 byte
84+ temp += hiResponseServerVersion # HiRespType 1 byte
85+ temp += b"\x00 " * 2 # Reserved1 2 bytes
86+ temp += b"\x00 " * 4 # Reserved2 4 bytes
87+ temp += aTime # TimeStamp 8 bytes
88+ temp += clientChallenge # ChallengeFromClient 8 bytes
89+ temp += b"\x00 " * 4 # Reserved 4 bytes
90+ temp += av_pairs .getData () # AvPairs variable
91+
92+ ntProofStr = ntlm .hmac_md5 (responseKeyNT , serverChallenge + temp )
93+
94+ ntChallengeResponse = ntProofStr + temp
95+ lmChallengeResponse = ntlm .hmac_md5 (responseKeyNT , serverChallenge + clientChallenge ) + clientChallenge
96+ sessionBaseKey = ntlm .hmac_md5 (responseKeyNT , ntProofStr )
97+
98+ if user == "" and password == "" :
99+ # Special case for anonymous authentication
100+ ntChallengeResponse = ""
101+ lmChallengeResponse = ""
102+
103+ return ntChallengeResponse , lmChallengeResponse , sessionBaseKey
104+
105+ def mod_getNTLMSSPType3 (type1 , type2 , user , password , domain , lmhash = "" , nthash = "" , use_ntlmv2 = ntlm .USE_NTLMv2 , channel_binding_value = b"" ):
106+ # Safety check in case somebody sent password = None.. That's not allowed. Setting it to '' and hope for the best.
107+ if password is None :
108+ password = ""
109+
110+ # Let's do some encoding checks before moving on. Kind of dirty, but found effective when dealing with
111+ # international characters.
112+ import sys
113+ encoding = sys .getfilesystemencoding ()
114+ if encoding is not None :
115+ try :
116+ user .encode ("utf-16le" )
117+ except Exception :
118+ user = user .decode (encoding )
119+ try :
120+ password .encode ("utf-16le" )
121+ except Exception :
122+ password = password .decode (encoding )
123+ try :
124+ domain .encode ("utf-16le" )
125+ except Exception :
126+ domain = user .decode (encoding )
127+
128+ ntlmChallenge = ntlm .NTLMAuthChallenge (type2 )
129+
130+ # Let's start with the original flags sent in the type1 message
131+ responseFlags = type1 ["flags" ]
132+
133+ # Token received and parsed. Depending on the authentication
134+ # method we will create a valid ChallengeResponse
135+ ntlmChallengeResponse = ntlm .NTLMAuthChallengeResponse (user , password , ntlmChallenge ["challenge" ])
136+
137+ clientChallenge = ntlm .b ("" .join ([random .choice (string .digits + string .ascii_letters ) for _ in range (8 )]))
138+
139+ serverName = ntlmChallenge ["TargetInfoFields" ]
140+
141+ ntResponse , lmResponse , sessionBaseKey = ntlm .computeResponse (ntlmChallenge ["flags" ], ntlmChallenge ["challenge" ],
142+ clientChallenge , serverName , domain , user , password ,
143+ lmhash , nthash , use_ntlmv2 , channel_binding_value = channel_binding_value )
144+
145+ # Let's check the return flags
146+ if (ntlmChallenge ["flags" ] & ntlm .NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY ) == 0 :
147+ # No extended session security, taking it out
148+ responseFlags &= 0xffffffff ^ ntlm .NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY
149+ if (ntlmChallenge ["flags" ] & ntlm .NTLMSSP_NEGOTIATE_128 ) == 0 :
150+ # No support for 128 key len, taking it out
151+ responseFlags &= 0xffffffff ^ ntlm .NTLMSSP_NEGOTIATE_128
152+ if (ntlmChallenge ["flags" ] & ntlm .NTLMSSP_NEGOTIATE_KEY_EXCH ) == 0 :
153+ # No key exchange supported, taking it out
154+ responseFlags &= 0xffffffff ^ ntlm .NTLMSSP_NEGOTIATE_KEY_EXCH
155+
156+ # drop the mic need to unset these flags
157+ # https://github.com/fortra/impacket/blob/master/impacket/examples/ntlmrelayx/clients/ldaprelayclient.py#L72
158+ if ntlmChallenge ["flags" ] & ntlm .NTLMSSP_NEGOTIATE_SEAL == ntlm .NTLMSSP_NEGOTIATE_SEAL :
159+ responseFlags ^= ntlm .NTLMSSP_NEGOTIATE_SEAL
160+ if ntlmChallenge ["flags" ] & ntlm .NTLMSSP_NEGOTIATE_SIGN == ntlm .NTLMSSP_NEGOTIATE_SIGN :
161+ responseFlags ^= ntlm .NTLMSSP_NEGOTIATE_SIGN
162+ if ntlmChallenge ["flags" ] & ntlm .NTLMSSP_NEGOTIATE_ALWAYS_SIGN == ntlm .NTLMSSP_NEGOTIATE_ALWAYS_SIGN :
163+ responseFlags ^= ntlm .NTLMSSP_NEGOTIATE_ALWAYS_SIGN
164+
165+
166+ keyExchangeKey = ntlm .KXKEY (ntlmChallenge ["flags" ], sessionBaseKey , lmResponse , ntlmChallenge ["challenge" ], password ,
167+ lmhash , nthash , use_ntlmv2 )
168+
169+ # Special case for anonymous login
170+ if user == "" and password == "" and lmhash == "" and nthash == "" :
171+ keyExchangeKey = b"\x00 " * 16
172+
173+
174+ if ntlmChallenge ["flags" ] & ntlm .NTLMSSP_NEGOTIATE_KEY_EXCH :
175+ exportedSessionKey = ntlm .b ("" .join ([random .choice (string .digits + string .ascii_letters ) for _ in range (16 )]))
176+ encryptedRandomSessionKey = ntlm .generateEncryptedSessionKey (keyExchangeKey , exportedSessionKey )
177+ else :
178+ encryptedRandomSessionKey = None
179+ exportedSessionKey = keyExchangeKey
180+
181+ ntlmChallengeResponse ["flags" ] = responseFlags
182+ ntlmChallengeResponse ["domain_name" ] = domain .encode ("utf-16le" )
183+ ntlmChallengeResponse ["host_name" ] = type1 .getWorkstation ().encode ("utf-16le" )
184+ if lmResponse == "" :
185+ ntlmChallengeResponse ["lanman" ] = b"\x00 "
186+ else :
187+ ntlmChallengeResponse ["lanman" ] = lmResponse
188+ ntlmChallengeResponse ["ntlm" ] = ntResponse
189+ if encryptedRandomSessionKey is not None :
190+ ntlmChallengeResponse ["session_key" ] = encryptedRandomSessionKey
191+
192+ return ntlmChallengeResponse , exportedSessionKey
0 commit comments