Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
ntlm.py506 linesDownload Raw Back to utils
1"""
2"""
3
4# Created on 2015.04.02
5#
6# Author: Giovanni Cannata
7#
8# Copyright 2015 - 2020 Giovanni Cannata
9#
10# This file is part of ldap3.
11#
12# ldap3 is free software: you can redistribute it and/or modify
13# it under the terms of the GNU Lesser General Public License as published
14# by the Free Software Foundation, either version 3 of the License, or
15# (at your option) any later version.
16#
17# ldap3 is distributed in the hope that it will be useful,
18# but WITHOUT ANY WARRANTY; without even the implied warranty of
19# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20# GNU Lesser General Public License for more details.
21#
22# You should have received a copy of the GNU Lesser General Public License
23# along with ldap3 in the COPYING and COPYING.LESSER files.
24# If not, see <http://www.gnu.org/licenses/>.
25
26# NTLMv2 authentication as per [MS-NLMP] (https://msdn.microsoft.com/en-us/library/cc236621.aspx)
27
28from struct import pack, unpack
29from platform import system, version
30from socket import gethostname
31from time import time
32import hmac
33import hashlib
34import binascii
35from os import urandom
36
37try:
38    from locale import getpreferredencoding
39    oem_encoding = getpreferredencoding()
40except Exception:
41    oem_encoding = 'utf-8'
42
43from ..protocol.formatters.formatters import format_ad_timestamp
44
45NTLM_SIGNATURE = b'NTLMSSP\x00'
46NTLM_MESSAGE_TYPE_NTLM_NEGOTIATE = 1
47NTLM_MESSAGE_TYPE_NTLM_CHALLENGE = 2
48NTLM_MESSAGE_TYPE_NTLM_AUTHENTICATE = 3
49
50FLAG_NEGOTIATE_56 = 31  # W
51FLAG_NEGOTIATE_KEY_EXCH = 30  # V
52FLAG_NEGOTIATE_128 = 29  # U
53FLAG_NEGOTIATE_VERSION = 25  # T
54FLAG_NEGOTIATE_TARGET_INFO = 23  # S
55FLAG_REQUEST_NOT_NT_SESSION_KEY = 22  # R
56FLAG_NEGOTIATE_IDENTIFY = 20  # Q
57FLAG_NEGOTIATE_EXTENDED_SESSIONSECURITY = 19  # P
58FLAG_TARGET_TYPE_SERVER = 17  # O
59FLAG_TARGET_TYPE_DOMAIN = 16  # N
60FLAG_NEGOTIATE_ALWAYS_SIGN = 15  # M
61FLAG_NEGOTIATE_OEM_WORKSTATION_SUPPLIED = 13  # L
62FLAG_NEGOTIATE_OEM_DOMAIN_SUPPLIED = 12  # K
63FLAG_NEGOTIATE_ANONYMOUS = 11  # J
64FLAG_NEGOTIATE_NTLM = 9  # H
65FLAG_NEGOTIATE_LM_KEY = 7  # G
66FLAG_NEGOTIATE_DATAGRAM = 6  # F
67FLAG_NEGOTIATE_SEAL = 5  # E
68FLAG_NEGOTIATE_SIGN = 4  # D
69FLAG_REQUEST_TARGET = 2  # C
70FLAG_NEGOTIATE_OEM = 1  # B
71FLAG_NEGOTIATE_UNICODE = 0  # A
72
73FLAG_TYPES = [FLAG_NEGOTIATE_56,
74              FLAG_NEGOTIATE_KEY_EXCH,
75              FLAG_NEGOTIATE_128,
76              FLAG_NEGOTIATE_VERSION,
77              FLAG_NEGOTIATE_TARGET_INFO,
78              FLAG_REQUEST_NOT_NT_SESSION_KEY,
79              FLAG_NEGOTIATE_IDENTIFY,
80              FLAG_NEGOTIATE_EXTENDED_SESSIONSECURITY,
81              FLAG_TARGET_TYPE_SERVER,
82              FLAG_TARGET_TYPE_DOMAIN,
83              FLAG_NEGOTIATE_ALWAYS_SIGN,
84              FLAG_NEGOTIATE_OEM_WORKSTATION_SUPPLIED,
85              FLAG_NEGOTIATE_OEM_DOMAIN_SUPPLIED,
86              FLAG_NEGOTIATE_ANONYMOUS,
87              FLAG_NEGOTIATE_NTLM,
88              FLAG_NEGOTIATE_LM_KEY,
89              FLAG_NEGOTIATE_DATAGRAM,
90              FLAG_NEGOTIATE_SEAL,
91              FLAG_NEGOTIATE_SIGN,
92              FLAG_REQUEST_TARGET,
93              FLAG_NEGOTIATE_OEM,
94              FLAG_NEGOTIATE_UNICODE]
95
96AV_END_OF_LIST = 0
97AV_NETBIOS_COMPUTER_NAME = 1
98AV_NETBIOS_DOMAIN_NAME = 2
99AV_DNS_COMPUTER_NAME = 3
100AV_DNS_DOMAIN_NAME = 4
101AV_DNS_TREE_NAME = 5
102AV_FLAGS = 6
103AV_TIMESTAMP = 7
104AV_SINGLE_HOST_DATA = 8
105AV_TARGET_NAME = 9
106AV_CHANNEL_BINDINGS = 10
107
108AV_TYPES = [AV_END_OF_LIST,
109            AV_NETBIOS_COMPUTER_NAME,
110            AV_NETBIOS_DOMAIN_NAME,
111            AV_DNS_COMPUTER_NAME,
112            AV_DNS_DOMAIN_NAME,
113            AV_DNS_TREE_NAME,
114            AV_FLAGS,
115            AV_TIMESTAMP,
116            AV_SINGLE_HOST_DATA,
117            AV_TARGET_NAME,
118            AV_CHANNEL_BINDINGS]
119
120AV_FLAG_CONSTRAINED = 0
121AV_FLAG_INTEGRITY = 1
122AV_FLAG_TARGET_SPN_UNTRUSTED = 2
123
124AV_FLAG_TYPES = [AV_FLAG_CONSTRAINED,
125                 AV_FLAG_INTEGRITY,
126                 AV_FLAG_TARGET_SPN_UNTRUSTED]
127
128
129def pack_windows_version(debug=False):
130    if debug:
131        if system().lower() == 'windows':
132            try:
133                major_release, minor_release, build = version().split('.')
134                major_release = int(major_release)
135                minor_release = int(minor_release)
136                build = int(build)
137            except Exception:
138                major_release = 5
139                minor_release = 1
140                build = 2600
141        else:
142            major_release = 5
143            minor_release = 1
144            build = 2600
145    else:
146        major_release = 0
147        minor_release = 0
148        build = 0
149
150    return pack('<B', major_release) + \
151           pack('<B', minor_release) + \
152           pack('<H', build) + \
153           pack('<B', 0) + \
154           pack('<B', 0) + \
155           pack('<B', 0) + \
156           pack('<B', 15)
157
158
159def unpack_windows_version(version_message):
160    if len(version_message) != 8:
161        raise ValueError('version field must be 8 bytes long')
162
163    if str is bytes:  # Python 2
164        return (unpack('<B', version_message[0])[0],
165                unpack('<B', version_message[1])[0],
166                unpack('<H', version_message[2:4])[0],
167                unpack('<B', version_message[7])[0])
168    else:  # Python 3
169        return (int(version_message[0]),
170                int(version_message[1]),
171                int(unpack('<H', version_message[2:4])[0]),
172                int(version_message[7]))
173
174
175class NtlmClient(object):
176    def __init__(self, domain, user_name, password):
177        self.client_config_flags = 0
178        self.exported_session_key = None
179        self.negotiated_flags = None
180        self.user_name = user_name
181        self.user_domain = domain
182        self.no_lm_response_ntlm_v1 = None
183        self.client_blocked = False
184        self.client_block_exceptions = []
185        self.client_require_128_bit_encryption = None
186        self.max_life_time = None
187        self.client_signing_key = None
188        self.client_sealing_key = None
189        self.sequence_number = None
190        self.server_sealing_key = None
191        self.server_signing_key = None
192        self.integrity = False
193        self.replay_detect = False
194        self.sequence_detect = False
195        self.confidentiality = False
196        self.datagram = False
197        self.identity = False
198        self.client_supplied_target_name = None
199        self.client_channel_binding_unhashed = None
200        self.unverified_target_name = None
201        self._password = password
202        self.server_challenge = None
203        self.server_target_name = None
204        self.server_target_info = None
205        self.server_version = None
206        self.server_av_netbios_computer_name = None
207        self.server_av_netbios_domain_name = None
208        self.server_av_dns_computer_name = None
209        self.server_av_dns_domain_name = None
210        self.server_av_dns_forest_name = None
211        self.server_av_target_name = None
212        self.server_av_flags = None
213        self.server_av_timestamp = None
214        self.server_av_single_host_data = None
215        self.server_av_channel_bindings = None
216        self.server_av_flag_constrained = None
217        self.server_av_flag_integrity = None
218        self.server_av_flag_target_spn_untrusted = None
219        self.current_encoding = None
220        self.client_challenge = None
221        self.server_target_info_raw = None
222
223    def get_client_flag(self, flag):
224        if not self.client_config_flags:
225            return False
226
227        if flag in FLAG_TYPES:
228            return True if self.client_config_flags & (1 << flag) else False
229
230        raise ValueError('invalid flag')
231
232    def get_negotiated_flag(self, flag):
233        if not self.negotiated_flags:
234            return False
235
236        if flag not in FLAG_TYPES:
237            raise ValueError('invalid flag')
238
239        return True if self.negotiated_flags & (1 << flag) else False
240
241    def get_server_av_flag(self, flag):
242        if not self.server_av_flags:
243            return False
244
245        if flag not in AV_FLAG_TYPES:
246            raise ValueError('invalid AV flag')
247
248        return True if self.server_av_flags & (1 << flag) else False
249
250    def set_client_flag(self, flags):
251        if type(flags) == int:
252            flags = [flags]
253        for flag in flags:
254            if flag in FLAG_TYPES:
255                self.client_config_flags |= (1 << flag)
256            else:
257                raise ValueError('invalid flag')
258
259    def reset_client_flags(self):
260        self.client_config_flags = 0
261
262    def unset_client_flag(self, flags):
263        if type(flags) == int:
264            flags = [flags]
265        for flag in flags:
266            if flag in FLAG_TYPES:
267                self.client_config_flags &= ~(1 << flag)
268            else:
269                raise ValueError('invalid flag')
270
271    def create_negotiate_message(self):
272        """
273        Microsoft MS-NLMP 2.2.1.1
274        """
275        self.reset_client_flags()
276        self.set_client_flag([FLAG_REQUEST_TARGET,
277                              FLAG_NEGOTIATE_56,
278                              FLAG_NEGOTIATE_128,
279                              FLAG_NEGOTIATE_NTLM,
280                              FLAG_NEGOTIATE_ALWAYS_SIGN,
281                              FLAG_NEGOTIATE_OEM,
282                              FLAG_NEGOTIATE_UNICODE,
283                              FLAG_NEGOTIATE_EXTENDED_SESSIONSECURITY])
284
285        message = NTLM_SIGNATURE  # 8 bytes
286        message += pack('<I', NTLM_MESSAGE_TYPE_NTLM_NEGOTIATE)  # 4 bytes
287        message += pack('<I', self.client_config_flags)  # 4 bytes
288        message += self.pack_field('', 40)  # domain name field  # 8 bytes
289        if self.get_client_flag(FLAG_NEGOTIATE_VERSION):  # version 8 bytes - used for debug in ntlm
290            message += pack_windows_version(True)
291        else:
292            message += pack_windows_version(False)
293        return message
294
295    def parse_challenge_message(self, message):
296        """
297        Microsoft MS-NLMP 2.2.1.2
298        """
299        if len(message) < 56:  # minimum size of challenge message
300            return False
301
302        if message[0:8] != NTLM_SIGNATURE:  # NTLM signature - 8 bytes
303            return False
304
305        if int(unpack('<I', message[8:12])[0]) != NTLM_MESSAGE_TYPE_NTLM_CHALLENGE:  # type of message - 4 bytes
306            return False
307
308        target_name_len, _, target_name_offset = self.unpack_field(message[12:20])  # targetNameFields - 8 bytes
309        self.negotiated_flags = unpack('<I', message[20:24])[0]  # negotiated flags - 4 bytes
310        self.current_encoding = 'utf-16-le' if self.get_negotiated_flag(
311            FLAG_NEGOTIATE_UNICODE) else oem_encoding  # set encoding
312
313        self.server_challenge = message[24:32]  # server challenge - 8 bytes
314        target_info_len, _, target_info_offset = self.unpack_field(message[40:48])  # targetInfoFields - 8 bytes
315        self.server_version = unpack_windows_version(message[48:56])
316        if self.get_negotiated_flag(FLAG_REQUEST_TARGET) and target_name_len:
317            self.server_target_name = message[target_name_offset: target_name_offset + target_name_len].decode(
318                self.current_encoding)
319        if self.get_negotiated_flag(FLAG_NEGOTIATE_TARGET_INFO) and target_info_len:
320            self.server_target_info_raw = message[target_info_offset: target_info_offset + target_info_len]
321            self.server_target_info = self.unpack_av_info(self.server_target_info_raw)
322            for attribute, value in self.server_target_info:
323                if attribute == AV_NETBIOS_COMPUTER_NAME:
324                    self.server_av_netbios_computer_name = value.decode('utf-16-le')  # always unicode
325                elif attribute == AV_NETBIOS_DOMAIN_NAME:
326                    self.server_av_netbios_domain_name = value.decode('utf-16-le')  # always unicode
327                elif attribute == AV_DNS_COMPUTER_NAME:
328                    self.server_av_dns_computer_name = value.decode('utf-16-le')  # always unicode
329                elif attribute == AV_DNS_DOMAIN_NAME:
330                    self.server_av_dns_domain_name = value.decode('utf-16-le')  # always unicode
331                elif attribute == AV_DNS_TREE_NAME:
332                    self.server_av_dns_forest_name = value.decode('utf-16-le')  # always unicode
333                elif attribute == AV_FLAGS:
334                    if self.get_server_av_flag(AV_FLAG_CONSTRAINED):
335                        self.server_av_flag_constrained = True
336                    if self.get_server_av_flag(AV_FLAG_INTEGRITY):
337                        self.server_av_flag_integrity = True
338                    if self.get_server_av_flag(AV_FLAG_TARGET_SPN_UNTRUSTED):
339                        self.server_av_flag_target_spn_untrusted = True
340                elif attribute == AV_TIMESTAMP:
341                    self.server_av_timestamp = format_ad_timestamp(unpack('<Q', value)[0])
342                elif attribute == AV_SINGLE_HOST_DATA:
343                    self.server_av_single_host_data = value
344                elif attribute == AV_TARGET_NAME:
345                    self.server_av_target_name = value.decode('utf-16-le')  # always unicode
346                elif attribute == AV_CHANNEL_BINDINGS:
347                    self.server_av_channel_bindings = value
348                else:
349                    raise ValueError('unknown AV type')
350
351    def create_authenticate_message(self):
352        """
353        Microsoft MS-NLMP 2.2.1.3
354        """
355        # 3.1.5.2
356        if not self.client_config_flags and not self.negotiated_flags:
357            return False
358
359        # 3.1.5.2
360        if self.get_client_flag(FLAG_NEGOTIATE_128) and not self.get_negotiated_flag(FLAG_NEGOTIATE_128):
361            return False
362
363        # 3.1.5.2
364        if (not self.server_av_netbios_computer_name or not self.server_av_netbios_domain_name) and self.server_av_flag_integrity:
365            return False
366
367        message = NTLM_SIGNATURE  # 8 bytes
368        message += pack('<I', NTLM_MESSAGE_TYPE_NTLM_AUTHENTICATE)  # 4 bytes
369        pos = 88  # payload starts at 88
370        # 3.1.5.2
371        if self.server_target_info:
372            lm_challenge_response = b''
373        else:
374            # computed LmChallengeResponse - todo
375            lm_challenge_response = b''
376
377        message += self.pack_field(lm_challenge_response, pos)  # LmChallengeResponseField field  # 8 bytes
378        pos += len(lm_challenge_response)
379        nt_challenge_response = self.compute_nt_response()
380        message += self.pack_field(nt_challenge_response, pos)  # NtChallengeResponseField field  # 8 bytes
381        pos += len(nt_challenge_response)
382        domain_name = self.user_domain.encode(self.current_encoding)
383        message += self.pack_field(domain_name, pos)  # DomainNameField field  # 8 bytes
384        pos += len(domain_name)
385        user_name = self.user_name.encode(self.current_encoding)
386        message += self.pack_field(user_name, pos)  # UserNameField field  # 8 bytes
387        pos += len(user_name)
388        if self.get_negotiated_flag(FLAG_NEGOTIATE_OEM_WORKSTATION_SUPPLIED) or self.get_negotiated_flag(
389                FLAG_NEGOTIATE_VERSION):
390            workstation = gethostname().encode(self.current_encoding)
391        else:
392            workstation = b''
393        message += self.pack_field(workstation, pos)  # empty WorkstationField field  # 8 bytes
394        pos += len(workstation)
395        encrypted_random_session_key = b''
396        message += self.pack_field(encrypted_random_session_key, pos)  # EncryptedRandomSessionKeyField field  # 8 bytes
397        pos += len(encrypted_random_session_key)
398        message += pack('<I', self.negotiated_flags)  # negotiated flags - 4 bytes
399        if self.get_negotiated_flag(FLAG_NEGOTIATE_VERSION):
400            message += pack_windows_version(True)  # windows version - 8 bytes
401        else:
402            message += pack_windows_version()  # empty windows version - 8 bytes
403        message += pack('<Q', 0)  # mic
404        message += pack('<Q', 0)  # mic - total of 16 bytes
405        # payload starts at 88
406        message += lm_challenge_response
407        message += nt_challenge_response
408        message += domain_name
409        message += user_name
410        message += workstation
411        message += encrypted_random_session_key
412
413        return message
414
415    @staticmethod
416    def pack_field(value, offset):
417        return pack('<HHI', len(value), len(value), offset)
418
419    @staticmethod
420    def unpack_field(field_message):
421        if len(field_message) != 8:
422            raise ValueError('ntlm field must be 8 bytes long')
423        return unpack('<H', field_message[0:2])[0], \
424               unpack('<H', field_message[2:4])[0], \
425               unpack('<I', field_message[4:8])[0]
426
427    @staticmethod
428    def unpack_av_info(info):
429        if info:
430            avs = list()
431            done = False
432            pos = 0
433            while not done:
434                av_type = unpack('<H', info[pos: pos + 2])[0]
435                if av_type not in AV_TYPES:
436                    raise ValueError('unknown AV type')
437                av_len = unpack('<H', info[pos + 2: pos + 4])[0]
438                av_value = info[pos + 4: pos + 4 + av_len]
439                pos += av_len + 4
440                if av_type == AV_END_OF_LIST:
441                    done = True
442                else:
443                    avs.append((av_type, av_value))
444        else:
445            return list()
446
447        return avs
448
449    @staticmethod
450    def pack_av_info(avs):
451        # avs is a list of tuples, each tuple is made of av_type and av_value
452        info = b''
453        for av_type, av_value in avs:
454            if av_type(0) == AV_END_OF_LIST:
455                continue
456            info += pack('<H', av_type)
457            info += pack('<H', len(av_value))
458            info += av_value
459
460        # add AV_END_OF_LIST
461        info += pack('<H', AV_END_OF_LIST)
462        info += pack('<H', 0)
463
464        return info
465
466    @staticmethod
467    def pack_windows_timestamp():
468        return pack('<Q', (int(time()) + 11644473600) * 10000000)
469
470    def compute_nt_response(self):
471        if not self.user_name and not self._password:  # anonymous authentication
472            return b''
473
474        self.client_challenge = urandom(8)
475        temp = b''
476        temp += pack('<B', 1)  # ResponseVersion - 1 byte
477        temp += pack('<B', 1)  # HiResponseVersion - 1 byte
478        temp += pack('<H', 0)  # Z(2)
479        temp += pack('<I', 0)  # Z(4) - total Z(6)
480        temp += self.pack_windows_timestamp()  # time - 8 bytes
481        temp += self.client_challenge  # random client challenge - 8 bytes
482        temp += pack('<I', 0)  # Z(4)
483        temp += self.server_target_info_raw
484        temp += pack('<I', 0)  # Z(4)
485        response_key_nt = self.ntowf_v2()
486        nt_proof_str = hmac.new(response_key_nt, self.server_challenge + temp, digestmod=hashlib.md5).digest()
487        nt_challenge_response = nt_proof_str + temp
488        return nt_challenge_response
489
490    def ntowf_v2(self):
491        passparts = self._password.split(':')
492        if len(passparts) == 2 and len(passparts[0]) == 32 and len(passparts[1]) == 32:
493            # The specified password is an LM:NTLM hash
494            password_digest = binascii.unhexlify(passparts[1])
495        else:
496            try:
497                password_digest = hashlib.new('MD4', self._password.encode('utf-16-le')).digest()
498            except ValueError as e:
499                try:
500                    from Crypto.Hash import MD4  # try with the Crypto library if present
501                    password_digest = MD4.new(self._password.encode('utf-16-le')).digest()
502                except ImportError:
503                    raise e  # raise original exception
504
505        return hmac.new(password_digest, (self.user_name.upper() + self.user_domain).encode('utf-16-le'), digestmod=hashlib.md5).digest()
506 
codekingpro/portable-devtools · Team Ai