Development of an internal social media platform with personalised dashboards for students
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ntlm.py 19KB

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