【安全通报】Apache Kylin 远程代码执行和SQL注入漏洞

Image

近期Apache Kylin新披露两个安全漏洞,分别是命令注入漏洞和 Hive SQL 注入漏洞。

关于Apache Kylin

Apache Kylin 最早由 eBay 于 2013 创建并且开源,2015 年成为 Apache 基金会的顶级项目。他是目前大数据领域应用非常广泛的开源分布式的分析型数据仓库,能够提供提供Hadoop/Spark 之上的 SQL 查询接口及多维分析(OLAP)能力。近年来,大数据行业方兴未艾,Apache Kylin被国内外的很多大型互联网企业广泛应用,被业界称为大数据分析界的“神兽”。

• 主页:https://kylin.apache.org/

• 源码:https://github.com/apache/kylin

• 《Apache Kylin权威指南(第2版)》: https://book.douban.com/subject/34804888/

SQL 注入漏洞


CVE编号

CVE-2020-13926

漏洞类型

SQL 注入

重要程度

重要

影响 Kylin 版本

Kylin 2.3.0 至 2.3.2

Kylin 2.4.0 至 2.4.1

Kylin 2.5.0 至 2.5.2

Kylin 2.6.0 至 2.6.6

Kylin 3.0.0-alpha

Kylin 3.0.0-alpha2

Kylin 3.0.0-beta

Kylin 3.0.0 至 3.0.2

问题描述

上述 Kylin 版本中存在通过读取配置属性作为参数拼接 Hive SQL 的操作,这可能使得攻击者可以通过在这些配置参数拼接上额外的 SQL 指令,进行未经授权的查询等操作。

处理方法(建议采取方法一)

方法一:

升级到 3.1.0 版本。
 
方法二:

目前修复代码已经更新到 Apache Kylin master 代码分支以及 2.6.x 和 3.0.x 分支,有需要的用户可以自行打包安装使用。

命令注入漏洞


CVE 编号

CVE-2020-13925

漏洞类型

命令注入

重要程度

重要

影响 Kylin 版本

Kylin 2.3.0 至 2.3.2

Kylin 2.4.0 至 2.4.1

Kylin 2.5.0 至 2.5.2

Kylin 2.6.0 至 2.6.6

Kylin 3.0.0-alpha

Kylin 3.0.0-alpha2

Kylin 3.0.0-beta

Kylin 3.0.0 至 3.0.2

问题描述

上述 Kylin 版本中存在 Restful API 会通过拼接命令进行操作,这可能使得攻击者可以通过在这些 API 的参数拼接上额外的命令元素,进行未经授权的操作。

处理方法

方法一:

升级到 3.1.0 版本。
 
方法二:

目前修复代码已经更新到 Apache Kylin master 代码分支以及 2.6.x 和 3.0.x 分支,有需要的用户可以自行打包安装使用。

漏洞分析

Kylin 系统提供了一个前后端分离的 WEB UI,用户可以在上面管理项目、创建模型、分析数据等。

系统提供了一组系统诊断接口,用于在发生故障时获取项目、任务、操作系统的诊断信息,方便调试。

漏洞在于其中两个接口没有对输入参数做安全检查,并且在后续使用过程中拼接到了一个字符串中作为系统命令执行。黑客可以通过构造恶意参数值调用该接口,实现远程执行任意系统命令,获得运行 Apache Kylin 系统的操作系统账号权限。

调用这个两个漏洞接口,需要有账号能够登陆 WEB 系统,但因为该 WEB 系统在安装完成后或部署 docker 容器后会有一个默认管理员账号 admin,并且会设置固定的默认密码 "KYLIN",如果管理员没有特意修改,则黑客可以直接登陆并利用漏洞。也可能被通过其他方式得到账号或Session的黑客或内鬼利用获得更高权限。

因为 Apache Kylin 是一个大数据分析平台,需要连接数据源,可能在一些大型企业内直接接触核心数据,如果被黑客攻破,会造成很高的数据安全风险。


我们以修复前的最后一个版本 3.0.2 为例分析代码:

https://github.com/apache/kylin/tree/kylin-3.0.2

漏洞存在于两个接口中:* /kylin/api/diag/project/{project}/download * /kylin/api/diag/job/{jobId}/download

两个接口漏洞路径一致,我们以第一个为例,代码:

https://github.com/apache/kylin/blob/kylin-3.0.2/server-ba se/src/main/java/org/apache/kylin/rest/controller/DiagnosisController.java
@RequestMapping(value = "/project/{project}/download", method = { RequestMethod.GET }, produces = {"application/json" })
@ResponseBody
public void dumpProjectDiagnosisInfo(@PathVariable String project, final HttpServletRequest request,
        final HttpServletResponse response) {
    try (AutoDeleteDirectory diagDir = new AutoDeleteDirectory("diag_project", "")) {
        String filePath = dgService.dumpProjectDiagnosisInfo(project, diagDir.getFile());
        setDownloadResponse(filePath, response);
    } catch (IOException e) {
        throw new InternalErrorException("Failed to dump project diagnosis info. " + e.getMessage(), e);
    }
}


{project} 是一个路径参数,被映射为变量 project,没有做任何处理直接传入 dgService 的 dumpProjectDiagnosisInfo 方法:

https://github.com/apache/kylin/blob/kylin-3.0.2/server-ba se/src/main/java/org/apache/kylin/rest/service/DiagnosisService.javapublic String dumpProjectDiagnosisInfo(String project, File exportPath) throws IOException { aclEvaluate.checkProjectOperationPermission(project); String[] args = { project, exportPath.getAbsolutePath() }; runDiagnosisCLI(args); return getDiagnosisPackageName(exportPath);}

project 参数被插入 args 数组传入 runDiagnosisCLI,从这个方法名字可以看出是要执行命令了。但是在这之前有一个检查用户是否有项目操作权限的操作 checkProjectOperationPermission:

public void checkProjectOperationPermission(String projectName) {
    ProjectInstance projectInstance = getProjectInstance(projectName);
    aclUtil.hasProjectOperationPermission(projectInstance);
}

获取 project instance

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); } }

这里实际是在一个 map 中查询,如果我们输入 project 是 poc 或 exp,当然是不存在的,会返回 null,回到刚才的权限检查入口 checkProjectOperationPermission,得到的 projectInstance 为 null,然后传入 aclUtil.hasProjectOperationPermission:

@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;}


这个比较有意思,这里只是用 PreAuthorize 检查了用户身份,只要用户是 admin,或拥有 administration/management/operation 权限,就会返回 true,可能是 project 权限功能还没有实现,用检查用户身份方式暂时替代。

所以只要用户拥有以上用户身份或权限,即使我们输入的 project 不存在也可以通过权限检查。

接下来看 runDiagnosisCLI 如何执行命令

private void runDiagnosisCLI(String[] args) throws IOException {
    Message msg = MsgPicker.getMsg();
    File cwd = new File("");
    logger.debug("Current path: {}", cwd.getAbsolutePath());
    logger.debug("DiagnosisInfoCLI args: {}", Arrays.toString(args));
    File sc ript = new File(KylinConfig.getKylinHome() + File.separator + "bin", "diag.sh");
    if (!sc ript.exists()) {
        throw new BadRequestException(
                String.format(Locale.ROOT, msg.getDIAG_NOT_FOUND(), sc ript.getAbsolutePath()));    }
    String diagCmd = sc ript.getAbsolutePath() + " " + StringUtils.join(args, " ");
    CliCommandExecutor executor = KylinConfig.getInstanceFromEnv().getCliCommandExecutor();
    Pair<Integer, String> cmdOutput = executor.execute(diagCmd);
    if (cmdOutput.getFirst() != 0) {
        throw new BadRequestException(msg.getGENERATE_DIAG_PACKAGE_FAIL());
    }}
public Pair<Integer, String> execute(String command) throws IOException {
    return execute(command, new SoutLogger());}
public Pair<Integer, String> execute(String command, Logger logAppender) throws IOException {
    Pair<Integer, String> r;
    if (remoteHost == null) {
        r = runNativeCommand(command, logAppender);
    } else {
        r = runRemoteCommand(command, logAppender);
    }
    if (r.getFirst() != 0)
        throw new IOException("OS command error exit with return code: " + r.getFirst() //                + ", error message: " + r.getSecond() + "The command is: \n" + command                + (remoteHost == null ? "" : " (remoteHost:" + remoteHost + ")") //        );    return r;}

这里有一个 remoteHost,这个类提供了通过 ssh 在其他服务器执行命令的功能,但我们的场景没有使用,进入 runNativeCommand:

private Pair<Integer, String> runNativeCommand(String command, Logger logAppender) throws IOException { String[] cmd = new String[3]; String osName = System.getProperty("os.name"); if (osName.startsWith("Windows")) { cmd[0] = "cmd.exe"; cmd[1] = "/C"; } else { cmd[0] = "/bin/bash"; cmd[1] = "-c"; } cmd[2] = command;
ProcessBuilder builder = new ProcessBuilder(cmd); builder.redirectErrorStream(true); Process proc = builder.start();
// ...}

这里我们输入的 project 参数最终被拼接到了字符串中,作为命令用 java.lang.ProcessBuilder 执行。

修复方案

升级 Apache Kylin 系统到最新版本

https://github.com/apache/kylin/releases

https://hub.docker.com/r/apachekylin/apache-kylin-standalone/tags

Timeline

• 2020-06-02 京东安全蓝军向 Apache Security Team 报告漏洞

• 2020-06-09 Apache Kylin 官方确认漏洞

• 2020-06-27 Apache Kylin 官方放出修复版本3.1.0

• 2020-07-14 Apache Kylin官方公布漏洞,编号CVE-2020-13925

参考

[1] https://mp.weixin.qq.com/s/wMX68NrTk3n0P7lak4Zoxg

[2] https://mp.weixin.qq.com/s/LdEgENX2_b8tb12n4H9KJQ

白帽汇从事信息安全,专注于安全大数据、企业威胁情报。

公司产品:FOFA-网络空间安全搜索引擎、FOEYE-网络空间检索系统、NOSEC-安全讯息平台。

为您提供:网络空间测绘、企业资产收集、企业威胁情报、应急响应服务。

免责声明:文章内容不代表本站立场,本站不对其内容的真实性、完整性、准确性给予任何担保、暗示和承诺,仅供读者参考,文章版权归原作者所有。如本文内容影响到您的合法权益(内容、图片等),请及时联系本站,我们会及时删除处理。查看原文

为您推荐