# Copyright: (c) 2018, Jordan Borean (@jborean93) # MIT License (see LICENSE or https://opensource.org/licenses/MIT) import base64 import calendar import hashlib import hmac import os import struct import time import ntlm_auth.compute_hash as comphash import ntlm_auth.compute_keys as compkeys import ntlm_auth.messages from ntlm_auth.des import DES from ntlm_auth.constants import AvId, AvFlags, NegotiateFlags from ntlm_auth.gss_channel_bindings import GssChannelBindingsStruct class ComputeResponse(): def __init__(self, user_name, password, domain_name, challenge_message, ntlm_compatibility): """ Constructor for the response computations. This class will compute the various nt and lm challenge responses. :param user_name: The user name of the user we are trying to authenticate with :param password: The password of the user we are trying to authenticate with :param domain_name: The domain name of the user account we are authenticated with, default is None :param challenge_message: A ChallengeMessage object that was received from the server after the negotiate_message :param ntlm_compatibility: The Lan Manager Compatibility Level, used to determine what NTLM auth version to use, see Ntlm in ntlm.py for more details """ self._user_name = user_name self._password = password self._domain_name = domain_name self._challenge_message = challenge_message self._negotiate_flags = challenge_message.negotiate_flags self._server_challenge = challenge_message.server_challenge self._server_target_info = challenge_message.target_info self._ntlm_compatibility = ntlm_compatibility self._client_challenge = os.urandom(8) def get_lm_challenge_response(self): """ [MS-NLMP] v28.0 2016-07-14 3.3.1 - NTLM v1 Authentication 3.3.2 - NTLM v2 Authentication This method returns the LmChallengeResponse key based on the ntlm_compatibility chosen and the target_info supplied by the CHALLENGE_MESSAGE. It is quite different from what is set in the document as it combines the NTLMv1, NTLM2 and NTLMv2 methods into one and calls separate methods based on the ntlm_compatibility flag chosen. :return: response (LmChallengeResponse) - The LM response to the server challenge. Computed by the client """ if self._negotiate_flags & \ NegotiateFlags.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY and \ self._ntlm_compatibility < 3: response = self._get_LMv1_with_session_security_response( self._client_challenge ) elif 0 <= self._ntlm_compatibility <= 1: response = self._get_LMv1_response(self._password, self._server_challenge) elif self._ntlm_compatibility == 2: # Based on the compatibility level we don't want to use LM # responses, ignore the session_base_key as it is returned in nt response, ignore_key = \ self._get_NTLMv1_response(self._password, self._server_challenge) else: """ [MS-NLMP] v28.0 page 45 - 2016-07-14 3.1.5.12 Client Received a CHALLENGE_MESSAGE from the Server If NTLMv2 authentication is used and the CHALLENGE_MESSAGE TargetInfo field has an MsvAvTimestamp present, the client SHOULD NOT send the LmChallengeResponse and SHOULD send Z(24) instead. """ response = self._get_LMv2_response(self._user_name, self._password, self._domain_name, self._server_challenge, self._client_challenge) if self._server_target_info is not None: timestamp = \ self._server_target_info[AvId.MSV_AV_TIMESTAMP] if timestamp is not None: response = b'\x00' * 24 return response def get_nt_challenge_response(self, lm_challenge_response, server_certificate_hash=None, cbt_data=None): """ [MS-NLMP] v28.0 2016-07-14 3.3.1 - NTLM v1 Authentication 3.3.2 - NTLM v2 Authentication This method returns the NtChallengeResponse key based on the ntlm_compatibility chosen and the target_info supplied by the CHALLENGE_MESSAGE. It is quite different from what is set in the document as it combines the NTLMv1, NTLM2 and NTLMv2 methods into one and calls separate methods based on the ntlm_compatibility value chosen. :param lm_challenge_response: The LmChallengeResponse calculated beforehand, used to get the key_exchange_key value :param server_certificate_hash: This is deprecated and will be removed in a future version, use cbt_data instead :param cbt_data: The GssChannelBindingsStruct to bind in the NTLM response :return response: (NtChallengeResponse) - The NT response to the server challenge. Computed by the client :return session_base_key: (SessionBaseKey) - A session key calculated from the user password challenge :return target_info: (AV_PAIR) - The AV_PAIR structure used in the nt_challenge calculations """ if self._negotiate_flags & \ NegotiateFlags.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY and \ self._ntlm_compatibility < 3: # The compatibility level is less than 3 which means it doesn't # support NTLMv2 but we want extended security so use NTLM2 which # is different from NTLMv2 # [MS-NLMP] - 3.3.1 NTLMv1 Authentication response, session_base_key = \ self._get_NTLM2_response(self._password, self._server_challenge, self._client_challenge) lm_hash = comphash._lmowfv1(self._password) key_exchange_key = \ compkeys._get_exchange_key_ntlm_v1(self._negotiate_flags, session_base_key, self._server_challenge, lm_challenge_response, lm_hash) target_info = None elif 0 <= self._ntlm_compatibility < 3: response, session_base_key = \ self._get_NTLMv1_response(self._password, self._server_challenge) lm_hash = comphash._lmowfv1(self._password) key_exchange_key = \ compkeys._get_exchange_key_ntlm_v1(self._negotiate_flags, session_base_key, self._server_challenge, lm_challenge_response, lm_hash) target_info = None else: if self._server_target_info is None: target_info = ntlm_auth.messages.TargetInfo() else: target_info = self._server_target_info if target_info[AvId.MSV_AV_TIMESTAMP] is None: timestamp = get_windows_timestamp() else: timestamp = target_info[AvId.MSV_AV_TIMESTAMP] # [MS-NLMP] If the CHALLENGE_MESSAGE TargetInfo field has an # MsvAvTimestamp present, the client SHOULD provide a MIC target_info[AvId.MSV_AV_FLAGS] = \ struct.pack("