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# SECUREAUTH LABS. Copyright 2020 SecureAuth Corporation. All rights reserved. 

2# 

3# This software is provided under a slightly modified version 

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

5# for more information. 

6# 

7# Authors: 

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

9# 

10# Description: 

11# For MS-RPCH 

12# Can be programmed to be used in relay attacks 

13# 

14# Probably for future MAPI 

15 

16import re 

17import ssl 

18import base64 

19import binascii 

20 

21try: 

22 from http.client import HTTPConnection, HTTPSConnection 

23except ImportError: 

24 from httplib import HTTPConnection, HTTPSConnection 

25 

26from impacket import ntlm, LOG 

27 

28# Auth types 

29AUTH_AUTO = 'Auto' 

30AUTH_BASIC = 'Basic' 

31AUTH_NTLM = 'NTLM' 

32AUTH_NEGOTIATE = 'Negotiate' 

33AUTH_BEARER = 'Bearer' 

34AUTH_DIGEST = 'Digest' 

35 

36################################################################################ 

37# CLASSES 

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

39class HTTPClientSecurityProvider: 

40 def __init__(self, auth_type=AUTH_AUTO): 

41 self.__username = None 

42 self.__password = None 

43 self.__domain = None 

44 self.__lmhash = '' 

45 self.__nthash = '' 

46 self.__aesKey = '' 

47 self.__TGT = None 

48 self.__TGS = None 

49 

50 self.__auth_type = auth_type 

51 

52 self.__auth_types = [] 

53 self.__ntlmssp_info = None 

54 

55 def set_auth_type(self, auth_type): 

56 self.__auth_type = auth_type 

57 

58 def get_auth_type(self): 

59 return self.__auth_type 

60 

61 def get_auth_types(self): 

62 return self.__auth_types 

63 

64 def get_ntlmssp_info(self): 

65 return self.__ntlmssp_info 

66 

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

68 self.__username = username 

69 self.__password = password 

70 self.__domain = domain 

71 

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

73 if len(lmhash) % 2: 

74 lmhash = '0%s' % lmhash 

75 if len(nthash) % 2: 

76 nthash = '0%s' % nthash 

77 

78 try: # just in case they were converted already 

79 self.__lmhash = binascii.unhexlify(lmhash) 

80 self.__nthash = binascii.unhexlify(nthash) 

81 except: 

82 self.__lmhash = lmhash 

83 self.__nthash = nthash 

84 pass 

85 

86 self.__aesKey = aesKey 

87 self.__TGT = TGT 

88 self.__TGS = TGS 

89 

90 def parse_www_authenticate(self, header): 

91 ret = [] 

92 

93 if 'NTLM' in header: 

94 ret.append(AUTH_NTLM) 

95 if 'Basic' in header: 

96 ret.append(AUTH_BASIC) 

97 if 'Negotiate' in header: 

98 ret.append(AUTH_NEGOTIATE) 

99 if 'Bearer' in header: 

100 ret.append(AUTH_BEARER) 

101 if 'Digest' in header: 

102 ret.append(AUTH_DIGEST) 

103 

104 return ret 

105 

106 def connect(self, protocol, host_L6): 

107 if protocol == 'http': 

108 return HTTPConnection(host_L6) 

109 else: 

110 try: 

111 uv_context = ssl.SSLContext(ssl.PROTOCOL_SSLv23) 

112 return HTTPSConnection(host_L6, context=uv_context) 

113 except AttributeError: 

114 return HTTPSConnection(host_L6) 

115 

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

117 if self.__auth_type == AUTH_BASIC: 

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

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

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

121 else: 

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

123 

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

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

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

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

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

129 

130 if self.__domain == '': 

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

132 else: 

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

134 

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

136 

137 # Format: auth_headers, reserved, ... 

138 return {'Authorization': auth_line_http}, None 

139 

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

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

142 # be used in relay attacks 

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

144 auth_headers = headers.copy() 

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

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

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

148 res = http_obj.getresponse() 

149 res.read() 

150 

151 if res.status != 401: 

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

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

154 % (res.status, path) 

155 ) 

156 

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

158 raise Exception('No authentication requested by ' 

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

160 

161 if self.__auth_types == []: 

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

163 

164 if AUTH_NTLM not in self.__auth_types: 

165 # NTLM auth not supported for url 

166 return None, None 

167 

168 try: 

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

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

171 serverChallenge = base64.b64decode(serverChallengeBase64) 

172 except (IndexError, KeyError, AttributeError): 

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

174 

175 if not self.__ntlmssp_info: 

176 challenge = ntlm.NTLMAuthChallenge(serverChallenge) 

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

178 

179 # Format: serverChallenge, reserved, ... 

180 return serverChallenge, None 

181 

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

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

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

185 'cannot use Kerberos.') 

186 

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

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

189 

190 if serverChallenge is not None: 

191 self.__auth_type = AUTH_NTLM 

192 

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

194 self.__password, self.__domain, 

195 self.__lmhash, self.__nthash) 

196 

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

198 else: 

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

200 self.__auth_type = AUTH_BASIC 

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

202 else: 

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

204 

205 # Format: auth_headers, reserved, ... 

206 return {'Authorization': auth_line_http}, None