Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1# Impacket - Collection of Python classes for working with network protocols. 

2# 

3# SECUREAUTH LABS. Copyright (C) 2020 SecureAuth Corporation. All rights reserved. 

4# 

5# This software is provided under a slightly modified version 

6# of the Apache Software License. See the accompanying LICENSE file 

7# for more information. 

8# 

9# Description: 

10# For MS-RPCH 

11# Can be programmed to be used in relay attacks 

12# Probably for future MAPI 

13# 

14# Authors: 

15# Arseniy Sharoglazov <mohemiv@gmail.com> / Positive Technologies (https://www.ptsecurity.com/) 

16# 

17 

18import re 

19import ssl 

20import base64 

21import binascii 

22 

23try: 

24 from http.client import HTTPConnection, HTTPSConnection 

25except ImportError: 

26 from httplib import HTTPConnection, HTTPSConnection 

27 

28from impacket import ntlm, LOG 

29 

30# Auth types 

31AUTH_AUTO = 'Auto' 

32AUTH_BASIC = 'Basic' 

33AUTH_NTLM = 'NTLM' 

34AUTH_NEGOTIATE = 'Negotiate' 

35AUTH_BEARER = 'Bearer' 

36AUTH_DIGEST = 'Digest' 

37 

38################################################################################ 

39# CLASSES 

40################################################################################ 

41class HTTPClientSecurityProvider: 

42 def __init__(self, auth_type=AUTH_AUTO): 

43 self.__username = None 

44 self.__password = None 

45 self.__domain = None 

46 self.__lmhash = '' 

47 self.__nthash = '' 

48 self.__aesKey = '' 

49 self.__TGT = None 

50 self.__TGS = None 

51 

52 self.__auth_type = auth_type 

53 

54 self.__auth_types = [] 

55 self.__ntlmssp_info = None 

56 

57 def set_auth_type(self, auth_type): 

58 self.__auth_type = auth_type 

59 

60 def get_auth_type(self): 

61 return self.__auth_type 

62 

63 def get_auth_types(self): 

64 return self.__auth_types 

65 

66 def get_ntlmssp_info(self): 

67 return self.__ntlmssp_info 

68 

69 def set_credentials(self, username, password, domain='', lmhash='', nthash='', aesKey='', TGT=None, TGS=None): 

70 self.__username = username 

71 self.__password = password 

72 self.__domain = domain 

73 

74 if lmhash != '' or nthash != '': 

75 if len(lmhash) % 2: 

76 lmhash = '0%s' % lmhash 

77 if len(nthash) % 2: 

78 nthash = '0%s' % nthash 

79 

80 try: # just in case they were converted already 

81 self.__lmhash = binascii.unhexlify(lmhash) 

82 self.__nthash = binascii.unhexlify(nthash) 

83 except: 

84 self.__lmhash = lmhash 

85 self.__nthash = nthash 

86 pass 

87 

88 self.__aesKey = aesKey 

89 self.__TGT = TGT 

90 self.__TGS = TGS 

91 

92 def parse_www_authenticate(self, header): 

93 ret = [] 

94 

95 if 'NTLM' in header: 

96 ret.append(AUTH_NTLM) 

97 if 'Basic' in header: 

98 ret.append(AUTH_BASIC) 

99 if 'Negotiate' in header: 

100 ret.append(AUTH_NEGOTIATE) 

101 if 'Bearer' in header: 

102 ret.append(AUTH_BEARER) 

103 if 'Digest' in header: 

104 ret.append(AUTH_DIGEST) 

105 

106 return ret 

107 

108 def connect(self, protocol, host_L6): 

109 if protocol == 'http': 

110 return HTTPConnection(host_L6) 

111 else: 

112 try: 

113 uv_context = ssl.SSLContext(ssl.PROTOCOL_SSLv23) 

114 return HTTPSConnection(host_L6, context=uv_context) 

115 except AttributeError: 

116 return HTTPSConnection(host_L6) 

117 

118 def get_auth_headers(self, http_obj, method, path, headers): 

119 if self.__auth_type == AUTH_BASIC: 

120 return self.get_auth_headers_basic(http_obj, method, path, headers) 

121 elif self.__auth_type in [AUTH_AUTO, AUTH_NTLM]: 

122 return self.get_auth_headers_auto(http_obj, method, path, headers) 

123 else: 

124 raise Exception('%s auth type not supported' % self.__auth_type) 

125 

126 def get_auth_headers_basic(self, http_obj, method, path, headers): 

127 if self.__lmhash != '' or self.__nthash != '' or \ 

128 self.__aesKey != '' or self.__TGT != None or self.__TGS != None: 

129 raise Exception('Basic authentication in HTTP connection used, ' 

130 'so set a plaintext credentials to connect.') 

131 

132 if self.__domain == '': 

133 auth_line = self.__username + ':' + self.__password 

134 else: 

135 auth_line = self.__domain + '\\' + self.__username + ':' + self.__password 

136 

137 auth_line_http = 'Basic %s' % base64.b64encode(auth_line.encode('UTF-8')).decode('ascii') 

138 

139 # Format: auth_headers, reserved, ... 

140 return {'Authorization': auth_line_http}, None 

141 

142 # It's important that the class contains the separate method that 

143 # gets NTLM Type 1 value, as this way the class can be programmed to 

144 # be used in relay attacks 

145 def send_ntlm_type1(self, http_obj, method, path, headers, negotiateMessage): 

146 auth_headers = headers.copy() 

147 auth_headers['Content-Length'] = '0' 

148 auth_headers['Authorization'] = 'NTLM %s' % base64.b64encode(negotiateMessage).decode('ascii') 

149 http_obj.request(method, path, headers=auth_headers) 

150 res = http_obj.getresponse() 

151 res.read() 

152 

153 if res.status != 401: 

154 raise Exception('Status code returned: %d. ' 

155 'Authentication does not seem required for url %s' 

156 % (res.status, path) 

157 ) 

158 

159 if res.getheader('WWW-Authenticate') is None: 

160 raise Exception('No authentication requested by ' 

161 'the server for url %s' % path) 

162 

163 if self.__auth_types == []: 

164 self.__auth_types = self.parse_www_authenticate(res.getheader('WWW-Authenticate')) 

165 

166 if AUTH_NTLM not in self.__auth_types: 

167 # NTLM auth not supported for url 

168 return None, None 

169 

170 try: 

171 serverChallengeBase64 = re.search('NTLM ([a-zA-Z0-9+/]+={0,2})', 

172 res.getheader('WWW-Authenticate')).group(1) 

173 serverChallenge = base64.b64decode(serverChallengeBase64) 

174 except (IndexError, KeyError, AttributeError): 

175 raise Exception('No NTLM challenge returned from server for url %s' % path) 

176 

177 if not self.__ntlmssp_info: 

178 challenge = ntlm.NTLMAuthChallenge(serverChallenge) 

179 self.__ntlmssp_info = ntlm.AV_PAIRS(challenge['TargetInfoFields']) 

180 

181 # Format: serverChallenge, reserved, ... 

182 return serverChallenge, None 

183 

184 def get_auth_headers_auto(self, http_obj, method, path, headers): 

185 if self.__aesKey != '' or self.__TGT != None or self.__TGS != None: 

186 raise Exception('NTLM authentication in HTTP connection used, ' \ 

187 'cannot use Kerberos.') 

188 

189 auth = ntlm.getNTLMSSPType1(domain=self.__domain) 

190 serverChallenge = self.send_ntlm_type1(http_obj, method, path, headers, auth.getData())[0] 

191 

192 if serverChallenge is not None: 

193 self.__auth_type = AUTH_NTLM 

194 

195 type3, exportedSessionKey = ntlm.getNTLMSSPType3(auth, serverChallenge, self.__username, 

196 self.__password, self.__domain, 

197 self.__lmhash, self.__nthash) 

198 

199 auth_line_http = 'NTLM %s' % base64.b64encode(type3.getData()).decode('ascii') 

200 else: 

201 if self.__auth_type == AUTH_AUTO and AUTH_BASIC in self.__auth_types: 

202 self.__auth_type = AUTH_BASIC 

203 return self.get_auth_headers_basic(http_obj, method, path, headers) 

204 else: 

205 raise Exception('No supported auth offered by URL: %s' % self.__auth_types) 

206 

207 # Format: auth_headers, reserved, ... 

208 return {'Authorization': auth_line_http}, None