diff --git a/impacket/ldap/ldap.py b/impacket/ldap/ldap.py index 73b638066d..9a6f0dae74 100644 --- a/impacket/ldap/ldap.py +++ b/impacket/ldap/ldap.py @@ -310,8 +310,31 @@ def login(self, user='', password='', domain='', lmhash='', nthash='', authentic # NTLM Challenge type2 = response['bindResponse']['matchedDN'] + # If TLS is used, setup channel binding + channel_binding_value = b'' + if self._SSL: + # From: https://github.com/ly4k/ldap3/commit/87f5760e5a68c2f91eac8ba375f4ea3928e2b9e0#diff-c782b790cfa0a948362bf47d72df8ddd6daac12e5757afd9d371d89385b27ef6R1383 + from hashlib import md5 + # Ugly but effective, to get the digest of the X509 DER in bytes + peer_cert_digest_str = self._socket.get_peer_certificate().digest('sha256').decode() + peer_cert_digest_bytes = bytes.fromhex(peer_cert_digest_str.replace(':', '')) + + channel_binding_struct = b'' + initiator_address = b'\x00'*8 + acceptor_address = b'\x00'*8 + + # https://datatracker.ietf.org/doc/html/rfc5929#section-4 + application_data_raw = b'tls-server-end-point:' + peer_cert_digest_bytes + len_application_data = len(application_data_raw).to_bytes(4, byteorder='little', signed = False) + application_data = len_application_data + application_data += application_data_raw + channel_binding_struct += initiator_address + channel_binding_struct += acceptor_address + channel_binding_struct += application_data + channel_binding_value = md5(channel_binding_struct).digest() + # NTLM Auth - type3, exportedSessionKey = getNTLMSSPType3(negotiate, bytes(type2), user, password, domain, lmhash, nthash) + type3, exportedSessionKey = getNTLMSSPType3(negotiate, bytes(type2), user, password, domain, lmhash, nthash, channel_binding_value=channel_binding_value) bindRequest['authentication']['sicilyResponse'] = type3.getData() response = self.sendReceive(bindRequest)[0]['protocolOp'] elif authenticationChoice == 'sasl': diff --git a/impacket/ntlm.py b/impacket/ntlm.py index 94f2e458b6..040bc8f714 100644 --- a/impacket/ntlm.py +++ b/impacket/ntlm.py @@ -40,10 +40,10 @@ DEFAULT_LM_HASH = binascii.unhexlify('AAD3B435B51404EEAAD3B435B51404EE') def computeResponse(flags, serverChallenge, clientChallenge, serverName, domain, user, password, lmhash='', nthash='', - use_ntlmv2=USE_NTLMv2): + use_ntlmv2=USE_NTLMv2, channel_binding_value=b''): if use_ntlmv2: return computeResponseNTLMv2(flags, serverChallenge, clientChallenge, serverName, domain, user, password, - lmhash, nthash, use_ntlmv2=use_ntlmv2) + lmhash, nthash, use_ntlmv2=use_ntlmv2, channel_binding_value=channel_binding_value) else: return computeResponseNTLMv1(flags, serverChallenge, clientChallenge, serverName, domain, user, password, lmhash, nthash, use_ntlmv2=use_ntlmv2) @@ -597,7 +597,7 @@ def getNTLMSSPType1(workstation='', domain='', signingRequired = False, use_ntlm return auth -def getNTLMSSPType3(type1, type2, user, password, domain, lmhash = '', nthash = '', use_ntlmv2 = USE_NTLMv2): +def getNTLMSSPType3(type1, type2, user, password, domain, lmhash = '', nthash = '', use_ntlmv2 = USE_NTLMv2, channel_binding_value = b''): # Safety check in case somebody sent password = None.. That's not allowed. Setting it to '' and hope for the best. if password is None: @@ -636,7 +636,7 @@ def getNTLMSSPType3(type1, type2, user, password, domain, lmhash = '', nthash = ntResponse, lmResponse, sessionBaseKey = computeResponse(ntlmChallenge['flags'], ntlmChallenge['challenge'], clientChallenge, serverName, domain, user, password, - lmhash, nthash, use_ntlmv2) + lmhash, nthash, use_ntlmv2, channel_binding_value = channel_binding_value) # Let's check the return flags if (ntlmChallenge['flags'] & NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY) == 0: @@ -909,7 +909,7 @@ def LMOWFv2( user, password, domain, lmhash = ''): def computeResponseNTLMv2(flags, serverChallenge, clientChallenge, serverName, domain, user, password, lmhash='', - nthash='', use_ntlmv2=USE_NTLMv2): + nthash='', use_ntlmv2=USE_NTLMv2, channel_binding_value=b''): responseServerVersion = b'\x01' hiResponseServerVersion = b'\x01' @@ -930,9 +930,20 @@ def computeResponseNTLMv2(flags, serverChallenge, clientChallenge, serverName, d serverName = av_pairs.getData() else: aTime = b'\x00'*8 - - temp = responseServerVersion + hiResponseServerVersion + b'\x00' * 6 + aTime + clientChallenge + b'\x00' * 4 + \ - serverName + b'\x00' * 4 + + if len(channel_binding_value) > 0: + av_pairs[NTLMSSP_AV_CHANNEL_BINDINGS] = channel_binding_value + + # Format according to: + # https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/aee311d6-21a7-4470-92a5-c4ecb022a87b + temp = responseServerVersion # RespType 1 byte + temp += hiResponseServerVersion # HiRespType 1 byte + temp += b'\x00' * 2 # Reserved1 2 bytes + temp += b'\x00' * 4 # Reserved2 4 bytes + temp += aTime # TimeStamp 8 bytes + temp += clientChallenge # ChallengeFromClient 8 bytes + temp += b'\x00' * 4 # Reserved 4 bytes + temp += av_pairs.getData() # AvPairs variable ntProofStr = hmac_md5(responseKeyNT, serverChallenge + temp) diff --git a/tests/SMB_RPC/test_ntlm.py b/tests/SMB_RPC/test_ntlm.py index baaffac8a1..e1ee6400af 100644 --- a/tests/SMB_RPC/test_ntlm.py +++ b/tests/SMB_RPC/test_ntlm.py @@ -238,7 +238,7 @@ def test_ntlmv2(self): ntResponse, lmResponse, sessionBaseKey = ntlm.computeResponseNTLMv2(flags, self.serverChallenge, self.clientChallenge, serverName, self.domain, self.user, self.password, '', '' ) hexdump(sessionBaseKey) - self.assertEqual(sessionBaseKey, bytearray(b'\x8d\xe4\x0c\xca\xdb\xc1\x4a\x82\xf1\x5c\xb0\xad\x0d\xe9\x5c\xa3')) + self.assertEqual(sessionBaseKey, bytearray(b'\xe0\x02\x92\x35\xf1\x18\x08\xe6\x12\xea\xa1\xac\xe6\x13\x78\x5f')) print("\n") print("4.2.4.2.1 LMv2 Response") @@ -247,13 +247,13 @@ def test_ntlmv2(self): print("\n") print("4.2.4.2.2 NTLMv2 Response") hexdump(ntResponse[:16]) - self.assertEqual(ntResponse[:16], bytearray(b'\x68\xcd\x0a\xb8\x51\xe5\x1c\x96\xaa\xbc\x92\x7b\xeb\xef\x6a\x1c')) + self.assertEqual(ntResponse[:16], bytearray(b'\xb2\x32\x05\x0b\x98\xe5\xf4\xe3\x36\xbd\x18\x79\x21\xa2\x7b\xb2')) print("\n") print("4.2.4.2.3 Encrypted Session Key") keyExchangeKey = ntlm.KXKEY(flags, sessionBaseKey, lmResponse, self.serverChallenge, self.password,'','') encryptedSessionKey = ntlm.generateEncryptedSessionKey(keyExchangeKey,self.randomSessionKey) hexdump(encryptedSessionKey) - self.assertEqual(encryptedSessionKey, bytearray(b'\xC5\xDA\xD2\x54\x4F\xC9\x79\x90\x94\xCE\x1C\xE9\x0B\xC9\xD0\x3E')) + self.assertEqual(encryptedSessionKey, bytearray(b'\x18\x6c\xaf\xee\x66\x20\x16\x9d\xd9\x8c\x4d\x1a\x22\x56\x71\x4c')) print("\n") print("AUTHENTICATE MESSAGE") @@ -266,7 +266,7 @@ def test_ntlmv2(self): ntlmChallengeResponse['ntlm'] = ntResponse ntlmChallengeResponse['session_key'] = encryptedSessionKey hexdump(ntlmChallengeResponse.getData()) - self.assertEqual(ntlmChallengeResponse.getData(), bytearray(b'NTLMSSP\x00\x03\x00\x00\x00\x18\x00\x18\x00|\x00\x00\x00T\x00T\x00\x94\x00\x00\x00\x0c\x00\x0c\x00X\x00\x00\x00\x08\x00\x08\x00d\x00\x00\x00\x10\x00\x10\x00l\x00\x00\x00\x10\x00\x10\x00\xe8\x00\x00\x003\x82\x8a\xe2D\x00o\x00m\x00a\x00i\x00n\x00U\x00s\x00e\x00r\x00C\x00O\x00M\x00P\x00U\x00T\x00E\x00R\x00\x86\xc3P\x97\xac\x9c\xec\x10%TvJW\xcc\xcc\x19\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaah\xcd\n\xb8Q\xe5\x1c\x96\xaa\xbc\x92{\xeb\xefj\x1c\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00\x00\x02\x00\x0c\x00D\x00o\x00m\x00a\x00i\x00n\x00\x01\x00\x0c\x00S\x00e\x00r\x00v\x00e\x00r\x00\x00\x00\x00\x00\x00\x00\x00\x00\xc5\xda\xd2TO\xc9y\x90\x94\xce\x1c\xe9\x0b\xc9\xd0>')) + self.assertEqual(ntlmChallengeResponse.getData(), bytearray(b'NTLMSSP\x00\x03\x00\x00\x00\x18\x00\x18\x00|\x00\x00\x00P\x00P\x00\x94\x00\x00\x00\x0c\x00\x0c\x00X\x00\x00\x00\x08\x00\x08\x00d\x00\x00\x00\x10\x00\x10\x00l\x00\x00\x00\x10\x00\x10\x00\xe4\x00\x00\x003\x82\x8a\xe2D\x00o\x00m\x00a\x00i\x00n\x00U\x00s\x00e\x00r\x00C\x00O\x00M\x00P\x00U\x00T\x00E\x00R\x00\x86\xc3P\x97\xac\x9c\xec\x10%TvJW\xcc\xcc\x19\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xb22\x05\x0b\x98\xe5\xf4\xe36\xbd\x18y!\xa2{\xb2\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00\x00\x02\x00\x0c\x00D\x00o\x00m\x00a\x00i\x00n\x00\x01\x00\x0c\x00S\x00e\x00r\x00v\x00e\x00r\x00\x00\x00\x00\x00\x18l\xaf\xeef \x16\x9d\xd9\x8cM\x1a"VqL')) print("\n") print("4.2.4.4 GSS_WrapEx") print("Plaintext")