freeBuf
主站

分类

云安全 AI安全 开发安全 终端安全 数据安全 Web安全 基础安全 企业安全 关基安全 移动安全 系统安全 其他安全

特色

热点 工具 漏洞 人物志 活动 安全招聘 攻防演练 政策法规

点我创作

试试在FreeBuf发布您的第一篇文章 让安全圈留下您的足迹
我知道了

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

99+
Apache Kylin远程命令执行漏洞报告(CVE-2020-13925)
FreeBuf_329962 2020-07-16 11:35:11 529864

概要

6月,京东安全的蓝军团队发现了一个 apache kylin 远程命令执行严重漏洞( CVE-2020-13925)。黑客可以利用这个漏洞,登录任何管理员账号和密码默认未修改的账号,获得管理员权限。由于Apache Kylin被广泛应用于企业的大数据分析平台,因此该漏洞将对企业核心数据具有较大的危害,存在数据泄露风险,建议用户尽快升级软件至安全版本。

关于 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/

漏洞概述

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-base/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-base/src/main/java/org/apache/kylin/rest/service/DiagnosisService.java

public 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 script = new File(KylinConfig.getKylinHome() + File.separator + "bin", "diag.sh");
    if (!script.exists()) {
        throw new BadRequestException(
                String.format(Locale.ROOT, msg.getDIAG_NOT_FOUND(), script.getAbsolutePath()));
    }

    String diagCmd = script.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 执行。

影响范围

漏洞引入时间:

2016-04-29

影响版本:

3.0 - 2.3.2

4.0 - 2.4.1

5.0 - 2.5.2

6.0 - 2.6.4

0.0-alpha, 3.0.0-alpha2, 3.0.0-beta, 3.0.0

修复版本:

3.1.0

修复时间:

2020-06-27

修复方案

升级 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

References

https://kylin.apache.org/docs/security.html

http://kylin.apache.org/cn/docs/

https://github.com/apache/kylin

# 漏洞分析 # 漏洞利用 # 漏洞预警
本文为 FreeBuf_329962 独立观点,未经授权禁止转载。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
FreeBuf_329962 LV.2
京东安全应急响应中心。
  • 4 文章数
  • 6 关注者
探秘ROS安全系列(一)|机器人操作系统ROS及其安全研究演进
2021-04-02
零信任专场议题全解析,京麒沙龙第4期火热报名中~
2021-03-12
京麒沙龙零信任专题报名开启,腾讯、快手、京东安全专家在这儿等你!
2021-03-09