-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathhttp.py
208 lines (170 loc) · 7.59 KB
/
http.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
# Impacket - Collection of Python classes for working with network protocols.
#
# SECUREAUTH LABS. Copyright (C) 2020 SecureAuth Corporation. All rights reserved.
#
# This software is provided under a slightly modified version
# of the Apache Software License. See the accompanying LICENSE file
# for more information.
#
# Description:
# For MS-RPCH
# Can be programmed to be used in relay attacks
# Probably for future MAPI
#
# Authors:
# Arseniy Sharoglazov <mohemiv@gmail.com> / Positive Technologies (https://www.ptsecurity.com/)
#
import re
import ssl
import base64
import binascii
try:
from http.client import HTTPConnection, HTTPSConnection
except ImportError:
from httplib import HTTPConnection, HTTPSConnection
from impacket import ntlm, LOG
# Auth types
AUTH_AUTO = 'Auto'
AUTH_BASIC = 'Basic'
AUTH_NTLM = 'NTLM'
AUTH_NEGOTIATE = 'Negotiate'
AUTH_BEARER = 'Bearer'
AUTH_DIGEST = 'Digest'
################################################################################
# CLASSES
################################################################################
class HTTPClientSecurityProvider:
def __init__(self, auth_type=AUTH_AUTO):
self.__username = None
self.__password = None
self.__domain = None
self.__lmhash = ''
self.__nthash = ''
self.__aesKey = ''
self.__TGT = None
self.__TGS = None
self.__auth_type = auth_type
self.__auth_types = []
self.__ntlmssp_info = None
def set_auth_type(self, auth_type):
self.__auth_type = auth_type
def get_auth_type(self):
return self.__auth_type
def get_auth_types(self):
return self.__auth_types
def get_ntlmssp_info(self):
return self.__ntlmssp_info
def set_credentials(self, username, password, domain='', lmhash='', nthash='', aesKey='', TGT=None, TGS=None):
self.__username = username
self.__password = password
self.__domain = domain
if lmhash != '' or nthash != '':
if len(lmhash) % 2:
lmhash = '0%s' % lmhash
if len(nthash) % 2:
nthash = '0%s' % nthash
try: # just in case they were converted already
self.__lmhash = binascii.unhexlify(lmhash)
self.__nthash = binascii.unhexlify(nthash)
except:
self.__lmhash = lmhash
self.__nthash = nthash
pass
self.__aesKey = aesKey
self.__TGT = TGT
self.__TGS = TGS
def parse_www_authenticate(self, header):
ret = []
if 'NTLM' in header:
ret.append(AUTH_NTLM)
if 'Basic' in header:
ret.append(AUTH_BASIC)
if 'Negotiate' in header:
ret.append(AUTH_NEGOTIATE)
if 'Bearer' in header:
ret.append(AUTH_BEARER)
if 'Digest' in header:
ret.append(AUTH_DIGEST)
return ret
def connect(self, protocol, host_L6):
if protocol == 'http':
return HTTPConnection(host_L6)
else:
try:
uv_context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
return HTTPSConnection(host_L6, context=uv_context)
except AttributeError:
return HTTPSConnection(host_L6)
def get_auth_headers(self, http_obj, method, path, headers):
if self.__auth_type == AUTH_BASIC:
return self.get_auth_headers_basic(http_obj, method, path, headers)
elif self.__auth_type in [AUTH_AUTO, AUTH_NTLM]:
return self.get_auth_headers_auto(http_obj, method, path, headers)
else:
raise Exception('%s auth type not supported' % self.__auth_type)
def get_auth_headers_basic(self, http_obj, method, path, headers):
if self.__lmhash != '' or self.__nthash != '' or \
self.__aesKey != '' or self.__TGT != None or self.__TGS != None:
raise Exception('Basic authentication in HTTP connection used, '
'so set a plaintext credentials to connect.')
if self.__domain == '':
auth_line = self.__username + ':' + self.__password
else:
auth_line = self.__domain + '\\' + self.__username + ':' + self.__password
auth_line_http = 'Basic %s' % base64.b64encode(auth_line.encode('UTF-8')).decode('ascii')
# Format: auth_headers, reserved, ...
return {'Authorization': auth_line_http}, None
# It's important that the class contains the separate method that
# gets NTLM Type 1 value, as this way the class can be programmed to
# be used in relay attacks
def send_ntlm_type1(self, http_obj, method, path, headers, negotiateMessage):
auth_headers = headers.copy()
auth_headers['Content-Length'] = '0'
auth_headers['Authorization'] = 'NTLM %s' % base64.b64encode(negotiateMessage).decode('ascii')
http_obj.request(method, path, headers=auth_headers)
res = http_obj.getresponse()
res.read()
if res.status != 401:
raise Exception('Status code returned: %d. '
'Authentication does not seem required for url %s'
% (res.status, path)
)
if res.getheader('WWW-Authenticate') is None:
raise Exception('No authentication requested by '
'the server for url %s' % path)
if self.__auth_types == []:
self.__auth_types = self.parse_www_authenticate(res.getheader('WWW-Authenticate'))
if AUTH_NTLM not in self.__auth_types:
# NTLM auth not supported for url
return None, None
try:
serverChallengeBase64 = re.search('NTLM ([a-zA-Z0-9+/]+={0,2})',
res.getheader('WWW-Authenticate')).group(1)
serverChallenge = base64.b64decode(serverChallengeBase64)
except (IndexError, KeyError, AttributeError):
raise Exception('No NTLM challenge returned from server for url %s' % path)
if not self.__ntlmssp_info:
challenge = ntlm.NTLMAuthChallenge(serverChallenge)
self.__ntlmssp_info = ntlm.AV_PAIRS(challenge['TargetInfoFields'])
# Format: serverChallenge, reserved, ...
return serverChallenge, None
def get_auth_headers_auto(self, http_obj, method, path, headers):
if self.__aesKey != '' or self.__TGT != None or self.__TGS != None:
raise Exception('NTLM authentication in HTTP connection used, ' \
'cannot use Kerberos.')
auth = ntlm.getNTLMSSPType1(domain=self.__domain)
serverChallenge = self.send_ntlm_type1(http_obj, method, path, headers, auth.getData())[0]
if serverChallenge is not None:
self.__auth_type = AUTH_NTLM
type3, exportedSessionKey = ntlm.getNTLMSSPType3(auth, serverChallenge, self.__username,
self.__password, self.__domain,
self.__lmhash, self.__nthash)
auth_line_http = 'NTLM %s' % base64.b64encode(type3.getData()).decode('ascii')
else:
if self.__auth_type == AUTH_AUTO and AUTH_BASIC in self.__auth_types:
self.__auth_type = AUTH_BASIC
return self.get_auth_headers_basic(http_obj, method, path, headers)
else:
raise Exception('No supported auth offered by URL: %s' % self.__auth_types)
# Format: auth_headers, reserved, ...
return {'Authorization': auth_line_http}, None