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

image-20240326114008102

然后运行/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"

image-20240326114520110

修改完成后使用docker restart xx重启该容器即可

除了使用GitHub上的源码调试,还可以用docker里面运行的jar /opt/apps/backend-1.18.14.jar

漏洞分析

补丁分析

首先查看一下漏洞补丁

image-20240326120015431

在补丁中可以看到,修补的方式是添加了一个判断条件, getExtraParams() 返回的内容通过URL解码后是否存在于非法参数列表中,即黑名单

说实话,我并没有找到core/core-backend/src/main/java/io/dataease/datasource/type/Mysql.java这个文件

只找到了src/main/java/io/dataease/dto/datasource/MysqlConfiguration.java

image-20240326145839057

参数黑名单如下:

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的构造,说明这个是用来连接数据库的,在添加数据源的地方可以找到连接数据库的功能

image-20240326151716540

image-20240326151841423

这个额外的JDBC连接字符串可控,如果不存在黑名单,就大概率存在反序列化漏洞

验证漏洞很简单,将autoDeserialize=true编码然后填写到JDBC连接字符串中(等号不编码),autoDeserialize存在于黑名单中:

%61%75%74%6f%44%65%73%65%72%69%61%6c%69%7a%65=%74%72%75%65

点击校验,发现能够正常的得到连接响应,说明绕过了黑名单。

image-20240326155216351

这个漏洞我没有利用成功,可能是这个版本还太新了,这个版本利用的jdbc版本是8.0.28的,似乎没有反序列化漏洞

image-20240326155854650

经过一波摸索,发现这个可以自己上传驱动,漏洞实锤了

image-20240326163607911

直接使用工具就能够利用,读取文件需要添加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成功

image-20240326222203194

疑问解释

为什么jdbc连接时正常解析url编码?

这个需要调试跟踪一下

在构造完jdbc url后在io.dataease.provider.datasource.JdbcProvider.java进行连接,跟进connet方法

image-20240326162154614

来到com.mysql.cj.jdbc.NonRegisteringDriver

image-20240326173635715

首先来到accept(url) 判断这个url是否被允许请求

image-20240326173756963

继续跟进

image-20240326173831509

isConnectionStringSupported方法中通过正则匹配出jdbc:mysql:

image-20240326174036065

跟进decodeSkippingPlusSign

image-20240326174108561

这里会将+替换成%2B然后进行URLdecode

返回到isConnectionStringSupported

image-20240326174759809

跟进isSupported

image-20240326174955819

这个枚举了Scheme,看jdbc:mysql:是否存在。

返回到connect

image-20240326175355748

跟进getConnectionUrlInstance

image-20240326175520805

这个buildConnectionStringCacheKey方法把账号密码添加到了url中

image-20240326175557785

往下,根据返回的connStringCacheKey尝试从缓存中获取connectionUrl
没有缓存就通过parseConnectionString获取ConnectionUrlParser对象

image-20240326180035844

跟进parseConnectionString,此时的参数是connString ,即还没有加上账号密码的那个

image-20240326180505412

继续跟进ConnectionUrlParser()

image-20240326180634516

将connString赋值给this.baseConnectionString ,然后调用this.parseConnectionString() ,跟进

image-20240326181048646

这里对数据库名进行了解码,并且通过正则表达式将URL参数赋值部分取出保存在this.query

返回到getConnectionUrlInstance

刚刚解析得到的connStrParser传入到了getConnectionUrlInstance中进行处理

image-20240326214424293

跟进getConnectionUrlInstance

image-20240326215413683

因为info里面只有password和user,没有dnsSrv这个key,所有这里走的是else这个分支

(parsedProperties = parser.getProperties()).containsKey(dnsSrvPropKey.getKeyName() ,这个代码是从url参数进行查找dnsSrv,这里可能会对参数进行url解码

跟进这个getProperties()

image-20240326215948104

因为this.parsedProperties为null ,进入到this.parseQuerySection()中,继续跟进

image-20240326220250150

这里的this.query是上面正则表达式解析出来的URL参数 ,因为不为null 进入到了processKeyValuePattern中,PROPERTIES_PTRN的内容如下:

image-20240326220514764

跟进processKeyValuePattern

image-20240326220650132

这里根据正则表达式匹配出key和value ,并且通过decode()函数解码

image-20240326220853125

这个decode就是进行URLdecode的地方

image-20240326220950422

漏洞修复不完全

在我写这篇文章的时候,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())

END