CVE-2024-23328分析
CVE-2024-23328分析
漏洞概述:
DataEase是一款开源的数据可视化分析工具。DataEase数据源中存在一个反序列化漏洞,可以被利用来执行任意代码。漏洞代码位于core/core-backend/src/main/java/io/dataease/datasource/type/Mysql.java
文件中。可以绕过mysql jdbc攻击的黑名单,攻击者可以进一步利用该漏洞进行反序列化执行或读取任意文件。该漏洞已在1.18.15和2.3.0版本中修复。
调试环境搭建
去官网或github下载安装包和源代码,然后再linux部署,这里下载的是1.18.14版本的
下载完成后解压,然后编辑/detaease-vxxx/dataease/docker-compose.yml
,添加调试端口5005
然后运行/detaease-vxxx/install.sh
等待docker部署完成
docker部署后是有两个容器,进入detaease
的容器编辑文件/deployments/run-java.sh
,添加调试参数
在debug_options()
中添加:
echo "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005"
修改完成后使用docker restart xx
重启该容器即可
除了使用GitHub上的源码调试,还可以用docker里面运行的jar /opt/apps/backend-1.18.14.jar
漏洞分析
补丁分析
首先查看一下漏洞补丁
在补丁中可以看到,修补的方式是添加了一个判断条件, getExtraParams()
返回的内容通过URL解码后是否存在于非法参数列表中,即黑名单
说实话,我并没有找到core/core-backend/src/main/java/io/dataease/datasource/type/Mysql.java
这个文件
只找到了src/main/java/io/dataease/dto/datasource/MysqlConfiguration.java
参数黑名单如下:
private List<String> illegalParameters = Arrays.asList("autoDeserialize", "queryInterceptors", "statementInterceptors", "detectCustomCollations", "allowloadlocalinfile", "allowUrlInLocalInfile", "allowLoadLocalInfileInPath");
public List<String> getIllegalParameters(){
List<String> newIllegalParameters = new ArrayList<>();
newIllegalParameters.addAll(illegalParameters);
newIllegalParameters.addAll(Arrays.asList("allowloadlocalinfile", "allowUrlInLocalInfile", "allowLoadLocalInfileInPath"));
return newIllegalParameters;
}
补丁就加了个URL编码解码,说明漏洞利用点在于URL编码绕过了黑名单
漏洞验证
根据漏洞所在代码的功能找到对应功能点进行测试
这个是jdbc url的构造,说明这个是用来连接数据库的,在添加数据源的地方可以找到连接数据库的功能
这个额外的JDBC连接字符串
可控,如果不存在黑名单,就大概率存在反序列化漏洞
验证漏洞很简单,将autoDeserialize=true
编码然后填写到JDBC连接字符串中(等号不编码),autoDeserialize
存在于黑名单中:
%61%75%74%6f%44%65%73%65%72%69%61%6c%69%7a%65=%74%72%75%65
点击校验
,发现能够正常的得到连接响应,说明绕过了黑名单。
这个漏洞我没有利用成功,可能是这个版本还太新了,这个版本利用的jdbc版本是8.0.28的,似乎没有反序列化漏洞
经过一波摸索,发现这个可以自己上传驱动,漏洞实锤了
直接使用工具就能够利用,读取文件需要添加allowLoadLocalInfile=true
,该工具没给出
%61%6c%6c%6f%77%4c%6f%61%64%4c%6f%63%61%6c%49%6e%66%69%6c%65=%74%72%75%65
读取/etc/passwd成功
疑问解释
为什么jdbc连接时正常解析url编码?
这个需要调试跟踪一下
在构造完jdbc url后在io.dataease.provider.datasource.JdbcProvider.java
进行连接,跟进connet方法
来到com.mysql.cj.jdbc.NonRegisteringDriver
首先来到accept(url)
判断这个url是否被允许请求
继续跟进
在isConnectionStringSupported
方法中通过正则匹配出jdbc:mysql:
跟进decodeSkippingPlusSign
这里会将+替换成%2B然后进行URLdecode
返回到isConnectionStringSupported
跟进isSupported
这个枚举了Scheme,看jdbc:mysql:
是否存在。
返回到connect
跟进getConnectionUrlInstance
这个buildConnectionStringCacheKey
方法把账号密码添加到了url中
往下,根据返回的connStringCacheKey尝试从缓存中获取connectionUrl
没有缓存就通过parseConnectionString获取ConnectionUrlParser对象
跟进parseConnectionString
,此时的参数是connString
,即还没有加上账号密码的那个
继续跟进ConnectionUrlParser()
将connString赋值给this.baseConnectionString ,然后调用this.parseConnectionString()
,跟进
这里对数据库名进行了解码,并且通过正则表达式将URL参数赋值部分取出保存在this.query
中
返回到getConnectionUrlInstance
中
刚刚解析得到的connStrParser传入到了getConnectionUrlInstance
中进行处理
跟进getConnectionUrlInstance
因为info里面只有password和user,没有dnsSrv这个key,所有这里走的是else这个分支
(parsedProperties = parser.getProperties()).containsKey(dnsSrvPropKey.getKeyName()
,这个代码是从url参数进行查找dnsSrv
,这里可能会对参数进行url解码
跟进这个getProperties()
因为this.parsedProperties为null ,进入到this.parseQuerySection()
中,继续跟进
这里的this.query
是上面正则表达式解析出来的URL参数 ,因为不为null 进入到了processKeyValuePattern
中,PROPERTIES_PTRN
的内容如下:
跟进processKeyValuePattern
这里根据正则表达式匹配出key和value ,并且通过decode()函数解码
这个decode就是进行URLdecode的地方
漏洞修复不完全
在我写这篇文章的时候,DataEase已经更新到了2.4.1版本和1.18.17版本,即目前最新版本 ,按道理该漏洞已在1.18.15和2.3.0版本中修复
但是仔细发现这个漏洞修复是修复了个寂寞
位置:
2.4.1版本中的:core/core-backend/src/main/java/io/dataease/datasource/type/Mysql.java
1.18.17版本的: src/main/java/io/dataease/dto/datasource/MysqlConfiguration.java
当前代码:
public String getJdbc() {
if (StringUtils.isEmpty(extraParams.trim())) {
return "jdbc:mysql://HOSTNAME:PORT/DATABASE".replace("HOSTNAME", getHost().trim()).replace("PORT", getPort().toString().trim()).replace("DATABASE", getDataBase().trim());
} else {
for (String illegalParameter : getIllegalParameters()) {
if (getExtraParams().toLowerCase().contains(illegalParameter.toLowerCase()) || URLDecoder.decode(getExtraParams()).contains(illegalParameter.toLowerCase())) {
throw new RuntimeException("Illegal parameter: " + illegalParameter);
}
}
return "jdbc:mysql://HOSTNAME:PORT/DATABASE?EXTRA_PARAMS".replace("HOSTNAME", getHost().trim()).replace("PORT", getPort().toString().trim()).replace("DATABASE", getDataBase().trim()).replace("EXTRA_PARAMS", getExtraParams().trim());
}
}
问题出现在了这里:URLDecoder.decode(getExtraParams()).contains(illegalParameter.toLowerCase())
这里对额外的JDBC字符串进行URLdecode ,然后和转小写的黑名单进行对比。而解码后的JDBC字符串并没有转小写,所以漏洞根本就没有修复
目前该漏洞还是能够被利用。
修复建议:
将:
URLDecoder.decode(getExtraParams()).contains(illegalParameter.toLowerCase())
改为:
(URLDecoder.decode(getExtraParams()).toLowerCase()).contains(illegalParameter.toLowerCase())