Apache Kylin历史漏洞分析

1 前言

这里对Apache Kylin出现的两次漏洞进行下分析,本身漏洞也不难,而且前置条件需要用户登录,不过由于docker下的环境会存在一个默认账户(admin/KYLIN),所以这个登录条件的限制也不是那么的严格。在实际场景中如果能够遇到Kylin,配合上默认账户所以还是存在很大的安全隐患。 简单说下如何用docker部署Kylin,这里我部署的3.0.1版本,其中CVE-2020-1956影响的最高版本为3.0.1,CVE-2020-13925影响的最高版本为3.0.2

输入admin/KYLIN即可登录,登录后可以看到样例已经设定了两个Model方便学习。

2 CVE-2020-1956

漏洞代码位于kylin-kylin-3.0.1/server-base/src/main/java/org/apache/kylin/rest/service/CubeService.java第1085行

首先PreAuthorize里面定义了路由权限,其中可以看到包含ADMIN权限、ADMINISTRATION权限和MANAGEMENT权限可以访问该service。

接着1087行判断Kylin是否开启MigrateCube,如果没有开启,则返回错误,在默认环境下返回为false,因此想要利用该漏洞,得首先运用Kylin自带的环境变量设置,将kylin.tool.auto-migrate-cube.enabled设置为true。

然后1098行和1099行从环境变量中取出变量,这里由前面这个MigrateCube可知,环境变量我们均是可控的,因此srcCfgUri和dstCfgUri两个变量均是可控的,1101和1102行判断这两个变量是否为空,因此此处这两个变量一定不能为空值。

最后1105行开始拼接所有变量,可以看到这里创建了一个stringBuilder,然后经过format处理,最终进入到execute函数

默认情况下remoteHost为null,进入到runNativeCommand函数,跟到最后发现这里就是一个命令执行的函数。

那么回到最先的触发点,当我们控制了srcCfgUri和dstCfgUri,通过命令注入,那么最终就能执行我们设置的命令。

利用这里的"System"->"Set Config"功能进行环境变量的设置,首先需要开启migrate-cube,然后对srcCfgUri和dstCfgUri进行设置,将"kylin.tool.auto-migrate-cube.src-config"设置为“/tmp/123;touch /tmp/success;echo ",将"kylin.tool.auto-migrate-cube.dest-config"设置为"123456",最后再按照API格式请求路径即可,那么就会执行我们的touch命令,反弹shell同理可得。

3 CVE-2020-13925

漏洞代码位于kylin-kylin-3.0.1/server-base/src/main/java/org/apache/kylin/rest/controller/DiagnosisController.java第76行

这里project通过PathVariable注解得到,为可控变量,进入到82行,跟进dumpProjectDiagnosisInfo函数

首先通过checkProjectOperationPermission函数来检查该project是否许可,然后构建一个args的字符串数组,跟进runDiagnosisCLI函数

这里重点看110行,通过字符串拼接得到diagCmd,然后经过execute函数,这里execute函数的代码如上一个cve所示,简单讲就是一个命令执行的构造器,那么看到这里其实这个漏洞的挖掘思路就已经很明显了,上个cve暴露出了Kylin其实存在大量的字符串拼接,其次这个execute函数如果出现在用户可以控制的地方,那么就会存在命令注入的漏洞。

那么回到上面的checkProjectOperationPermission函数,看看里面到底是怎么样一个鉴权流程。

这里传入projectName,然后通过getProjectInstance来获取项目实例

那么这里由于projectName是我们需要污染的变量,所以传入的一定不会是一个正常的项目名,因此返回一定为null,进入到hasProjectOperationPermission函数

这里的鉴权其实并没有对ProjectInstance实例进行检验,而是操作权限进行了校验,当为ADMIN、ADMINISTRATION、MANAGEMENT、OPERATION等权限事,该值默认返回为true,所以这里的检验其实也是漏洞的根源。

最终在project位置处构造payload,这里最终拼接得到的命令语句如下

那么通过命令注入,使用||来执行or语句就能执行最终的命令

4 后记

在实践过程中发现Kylin经常会因为内存不够而闪退,这里推荐在起docker的时候不要加上-m参数指定内存,另外发现通报过程中提到了job的路由,但是在实践过程中这个路由并不会执行命令,而是会报错退出

这里显示在getProjectByJob时,由于返回为空,进而爆出java.lang.NullPointerException错误,并且根据函数调用显示,也没有走到最后的execute函数

其中getUuid会返回当前job的Uuid值,但是在构造poc的过程中,该值一定是不存在的,那么在这个时候并不会返回为空,而是会报上述错误,因此这里能否利用其实还要打一个问号。