# RCA Report: CVE-2026-40900

## Summary

CVE-2026-40900 is a stacked-query SQL injection vulnerability in DataEase's `previewSql` endpoint (`POST /de2api/datasetData/previewSql`). The endpoint accepts arbitrary user-supplied SQL and wraps it inside a subquery (`SELECT * FROM ( <USER_SQL> ) AS alias LIMIT 100`) without enforcing that the input is a single SELECT statement. When the underlying MySQL JDBC connection is configured with `allowMultiQueries=true`, an attacker can craft a payload that escapes the wrapping subquery using a closing parenthesis and semicolon, executes a second side-effecting statement (INSERT/UPDATE/DELETE), and uses a MySQL `#` comment to swallow the remainder of the wrapper. This grants full read/write access to the application database.

## Impact

- **Package/component affected**: DataEase (`io.dataease:datasource:type:Mysql` and `dataset:manage:DatasetDataManage`)
- **Affected versions**: `<= v2.10.20`
- **Fixed versions**: `v2.10.21`
- **Risk level**: High (CVSS 3.1: 8.8)
- **Consequences**: Authenticated attacker can execute arbitrary stacked SQL against the DataEase application database, including INSERT/UPDATE/DELETE on tables such as `core_msg_type` or Quartz scheduler tables, enabling further privilege escalation or RCE as documented in the Ox Security writeup.

## Root Cause

The vulnerability has two contributing factors:

1. **Missing `allowMultiQueries` in MySQL JDBC parameter blocklist**: In `core/core-backend/src/main/java/io/dataease/datasource/type/Mysql.java` (v2.10.20), the `illegalParameters` list did not include `allowMultiQueries`. This allowed an admin (or attacker who had compromised admin privileges via the preceding CVEs in the chain) to register a MySQL datasource whose JDBC URL contained `allowMultiQueries=true`.

2. **No single-statement validation in `previewSql`**: `DatasetDataManage.previewSql()` wraps the user-provided SQL string in a subquery without parsing or validating that it is a single SELECT statement. When `allowMultiQueries=true`, the MySQL JDBC driver happily executes multiple statements in one call.

The attacker payload:
```
SELECT 1 FROM dual) AS x; INSERT INTO repro_test (id, name) VALUES (999999999, 'pwned')#
```
becomes:
```
SELECT * FROM ( SELECT 1 FROM dual) AS x; INSERT INTO repro_test ... # ) AS alias LIMIT 100
```
The `#` comments out `) AS alias LIMIT 100`, leaving two valid statements, both of which MySQL executes.

**Fix commit**: `15611593b3631b5a25528b9cdb2ee517ef27929a` — adds `"allowMultiQueries"` to the `illegalParameters` list in `Mysql.java`. When the datasource configuration is validated during save/update, `JdbcUrlSecurityPolicy.validate()` now rejects any MySQL JDBC URL or extra parameters containing `allowMultiQueries`, causing the datasource to be saved with `status="Error"`. `previewSql` refuses to run against datasources with Error status, blocking the exploit path.

## Reproduction Steps

See `repro/reproduction_steps.sh` for the automated end-to-end reproduction. At a high level:

1. Start DataEase `v2.10.20` + MySQL via Docker Compose.
2. Log in as `admin` / `DataEase@123456` (RSA-encrypted credentials fetched via `/de2api/dekey`).
3. Create a MySQL datasource with `extraParams=allowMultiQueries=true`.
4. Validate the datasource (succeeds on v2.10.20).
5. Send `POST /de2api/datasetData/previewSql` with a base64-encoded stacked-SQL payload:
   ```
   SELECT 1 FROM dual) AS x; INSERT INTO repro_test (id, name) VALUES (999999999, 'pwned-by-cve-2026-40900')#
   ```
6. Query MySQL: a new row exists in `repro_test` — proving the INSERT executed.
7. Tear down, redeploy with `v2.10.21`, repeat the same save request.
8. On v2.10.21 the datasource is saved but marked `status="Error"` because `allowMultiQueries` is rejected by the updated `illegalParameters` blocklist. `previewSql` refuses to run, and no side-effect occurs.

## Evidence

- `logs/v2.10.20_exploit.json` — HTTP response from `previewSql` on the vulnerable build.
- `logs/v2.10.20_validate.json` — datasource validation response showing `status="Success"`.
- `logs/v2.10.21_create_datasource.json` — save response on the fixed build showing `status="Error"`.
- Console output from the reproduction script shows:
  - v2.10.20: `Post-exploit repro_test count: 1`
  - v2.10.21: datasource unusable, `count: 0`

## Recommendations / Next Steps

1. **Upgrade to v2.10.21 or later** — the vendor patch is minimal and targeted.
2. **Additional defense-in-depth**: add a server-side SQL parser (e.g., JSqlParser or Calcite SQL validation) to enforce that `previewSql` input contains exactly one SELECT statement before wrapping it.
3. **Audit existing MySQL datasources** for any that have `allowMultiQueries=true` in their configuration and remove or reconfigure them.
4. **Regression test**: include the stacked-SQL payload in the CI pipeline for `previewSql` to ensure future changes don't re-introduce the bypass.

## Additional Notes

- **Idempotency**: The script was run twice consecutively, both times producing the same results (vulnerable side-effect confirmed on v2.10.20, blocked on v2.10.21).
- **Limitations**: The reproduction requires running the full DataEase Spring Boot container and MySQL container, so each run takes ~60–90 seconds. The script handles cleanup automatically via `trap cleanup EXIT`.
- The fix is in the datasource configuration layer (`Mysql.illegalParameters`) rather than in the `previewSql` query-wrapping logic itself. While this blocks the specific `allowMultiQueries` vector, a more robust fix would also validate the SQL structure in `previewSql` to defend against other JDBC-parameter bypasses.
