CVE-2019-9670 zimbra XXE
漏洞原理
网上关于这个漏洞的审计似乎比较少,所以在分析漏洞原理的时候走了一些不必要的弯路。根据漏洞披露出的细节来看,在/Autodiscover中存在CVE-2019-9670,查找zimbra-core中带有Autodicover的类名:
find . -name “*.jar”|awk ‘{print “jar -tvf “$1}’ | sh -x | grep -i Autodiscover
找到如下两个jar包:
可以看出nashorn.jar是jre的中的jar包,应该不是我们的目标,而AutoDiscoverServlet带有Servlet字样,应是对外提供服务的。(这里纠结了很久,其实大致浏览一下代码,就可以确定哪个才是目标)
拿出zimbrastore.jar放入IDEA或者jd-gui进行反编译,其中doPost部分如下:
1 | public void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { |
注意如下部分:
这里获取了我们传入的Request标签中的元素,将EMailAddress标签的值和AcceptableResponseSchema标签的值分别赋值给email和responseSchema,若email为空或responseSchema为空,则直接返回400状态码及Body cannot be parsed。
我们可以传入如下body,查看响应:
此时就可以确认这个类是Autodiscover功能对应类。进入之后的if判断,代码如下:
这里对responseSchema也就是AcceptableResponseSchema标签中的值进行了判断,若不等于http://schemas.microsoft.com/exchange/autodiscover/mobilesync/responseschema/2006或者http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a则返回503以及Requested response schema not available再加上responseSchema的值。因此此处存在回显型xxe。假设传入如下xml:
1 |
|
此时email不为空且responseSchema的值为/etc/passwd的内容,攻击者可以成功读取/etc/passwd的值:
可以看到返回的结果正是Requested response schema not available + /etc/passwd,之后的adminToken的获取也正是基于此原理读取/opt/zimbra/conf/localconfig.xml文件获取zimbra_ldap_password。不过因为localconfig.xml文件会在解析器中被解析,无法以文本形式通过XXE漏洞被读出,需要加上<![CDATA[]]>,所以采用外部DTD的方式读取。
Pocsuite-POC
1 | #!/usr/bin/env python |
POC运行结果如下:
CVE-2022-27925 Zimbra路径穿越导致的RCE漏洞
漏洞原理
调试设置
1 | su zimbra |
之后在IDEA导入依赖库即可。
漏洞分析
漏洞出现在 mboximport 相关的功能中,全局搜索定位至com.zimbra.cs.service.backup.MailboxImportServlet:
由其命名规则和存在的成员函数来看,MailboxImportServlet应该对应一个HttpServlet对象,但是MailboxImportServlet继承于com.zimbra.cs.extension.ExtensionHttpHandler 而非 HttpServlet,因此需要找到如何通过URL关联到此处。
Zimbra 自定义了 Servlet 对象的基类 ZimbraServlet:
查看ZimbraServlet的子类:
定位至com.zimbra.cs.extension.ExtensionDispatcherServlet:
在webapps\service\WEB-INF\web.xml中可以找到:
因此com.zimbra.cs.extension.ExtensionDispatcherServlet对应的url规则是/service/extension/*,接着在com.zimbra.cs.extension.ExtensionDispatcherServlet#service中:
可以看到handler是一个ExtensionHttpHandler对象,而前面的 MailboxImportServlet 正好继承于 ExtensionHttpHandler。跟进com.zimbra.cs.extension.ExtensionDispatcherServlet#getHandler(javax.servlet.http.HttpServletRequest):
将url中的/service/extension之后的内容赋值给extPath,若extPath的长度不为0,则执行getHandler(extPath),并将结果赋值给handler。跟进com.zimbra.cs.extension.ExtensionDispatcherServlet#getHandler(java.lang.String):
其中sHandlers的定义及赋值如下所示:
sHandlers是一个Map类型的对象,在com.zimbra.cs.extension.ExtensionDispatcherServlet#register中对sHandlers的键值对进行了操作,其中key来自handler.getPath():
com.zimbra.cs.extension.ExtensionHttpHandler#getPath中存在this.mExtension,可以看到,其在init方法中进行了定义,其中ext是一个ZimbraExtension类型的对象。定位到ZimbraExtension的子类com.zimbra.cs.backup.BackupExtension:
可以看到在此处执行了ExtensionDispatcherServlet的register方法,并且传入的handler正好是一个MailboxImportServlet实例。那么进入register函数,首先执行handler.init(ext);,此时,this.mExtension是一个BackupExtension实例。之后执行String name = handler.getPath();,因为handler是一个MailboxImportServlet实例,MailboxImportServlet的getPath()方法如下所示:
com.zimbra.cs.extension.ExtensionHttpHandler的getPath()方法如下所示:
因为this.mExtension是一个BackupExtension实例,而BackupExtension的getName()方法如下所示:
因此最后name的值为/backup/mboximport,因此sHandlers的一个键值对为:/backup/mboximport => new MailboxImportServlet()。之后经过com.zimbra.cs.extension.ExtensionDispatcherServlet#getHandler(java.lang.String)的执行,就会返回一个MailboxImportServlet实例。接着继续审计com.zimbra.cs.extension.ExtensionDispatcherServlet#service:
根据上面分析,首先得到一个MailboxImportServlet实例handler,之后对请求的方法进行判断,如果是POST方法,则执行该MailboxImportServlet实例的doPost方法。在com.zimbra.cs.service.backup.MailboxImportServlet#doPost中,首先通过 getAuthTokenFromCookie 从 Cookie 中提取 token 认证信息,并检查是否为管理员权限:
若authToken为空或不是管理员,则返回401状态码以及no authtoken cookie,也就是如下情况:
接着代码继续执行:
获取account-name、account-status以及ow参数。接着判断account-name及ow是否为空,若不为空,进入else:
若我们不传入switch-onl、no-switch以及append,那么switchOnly、noSwitch以及append的值为默认值false。之后还获取了account,若为空,则直接返回,程序中断。因此我们传入的account-name必须是一个存在的用户。
接着执行之后的代码,不传入switch-onl参数,此时switchOnly的值为false,进入如下if条件:
在第152行执行了importFrom()方法,其中in为我们POST传入的值。跟进com.zimbra.cs.service.backup.MailboxImportServlet#importFrom:
source是一个ZipBackupTarget实例,构造函数如下:
注意到this.mTempDir = new File(path);,跟进com.zimbra.cs.account.ZAttrServer#getMailboxMoveTempDir:
可以看到path的值默认为/opt/zimbra/backup/tmp/mboxmove。因此mTempDir是一个File实例,代表/opt/zimbra/backup/tmp/mboxmove目录。
之后跟进com.zimbra.cs.backup.ZipBackupTarget#restore方法:
再次跟进com.zimbra.cs.backup.ZipBackupTarget#getAccountSession(java.lang.String):
返回一个RestoreAcctSession实例,构造方法如下所示:
执行了this.mTempDir = new File(ZipBackupTarget.this.getTempRoot(), accountId);,跟进com.zimbra.cs.backup.ZipBackupTarget#getTempRoot:
返回的是this.mTempDir,因此此时的this.mTempDir代表的是/opt/zimbra/backup/tmp/mboxmove/xxxxxxxxx目录,其中xxxxxxxxx为accountId。之后跟进com.zimbra.cs.backup.ZipBackupTarget.RestoreAcctSession#unzipToTempFiles:
可以看到此处存在路径穿越漏洞。假设我们将压缩包中文件的名字设为../1.txt,那么file对象指代带的就是/opt/zimbra/backup/tmp/mboxmove/xxxxxxxxx/../1.txt,也就是/opt/zimbra/backup/tmp/mboxmove/1.txt。之后跟进com.zimbra.common.util.FileUtil#copy(java.io.InputStream, boolean, java.io.File):
可以看到这里将文件内容写入到对应的文件中。因此利用思路已经很明了了。
我们需要以POST方法在/service/extension/backup/mboximport?account-name=admin&account-status=1&ow=xxx中传入一个压缩包,其中压缩包中有一个文件名为../../../../mailboxd/webapps/zimbraAdmin/1.jsp的文件,内容为普通的webshell payload即可,这样的话上面提到的file对象指代带的就是/opt/zimbra/mailboxd/webapps/zimbraAdmin/1.jsp,程序会将webshell payload写入到/opt/zimbra/mailboxd/webapps/zimbraAdmin/1.jsp中。
Pocsuite-POC
poc可以获取到管理员邮箱、ldap用户密码、mysql地址账号密码以及zimbra敏感配置文件localconfig.xml等敏感信息。
1 | #!/usr/bin/env python |
POC运行结果如下:
CVE-2022-41352 Zimbra Unauthenticated RCE
漏洞原理
在Zimbra Collaboration(ZCS)8.8.15 和 9.0中,攻击者可以利用cpio漏洞通过amavisd上传任意文件到web目录/opt/zimbra/jetty/webapps/zimbra/public,从而导致未授权远程代码执行。 Zimbra建议用户使用pax代替cpio,虽然安装pax是Ubuntu上安装Zimbra的前提条件,但是在RHEL 6(或CentOS 6)之后,Red Hat发行版不再默认安装pax。
Pocsuite-POC
1 | #!/usr/bin/env python |






































