# Variant RCA Report: CVE-2026-40900

## Summary

Three variant hypotheses were tested against DataEase v2.10.20 (vulnerable) and v2.10.21 (fixed). **Variant 3** — a PostgreSQL time-based stacked SQL injection through the same `previewSql` endpoint — was **confirmed as an alternate trigger** on v2.10.20, proving the root cause (lack of single-statement validation in `previewSql`) affects non-MySQL datasources as well. Variants 1 and 2 were partially successful on v2.10.20 but **blocked or non-exploitable on v2.10.21**. No true bypass of the v2.10.21 patch was found, though Variant 2 exposes a validation-logic bypass (double-URL-encoded `allowMultiQueries` passes the `illegalParameters` check while the driver does not recognize it).

## Fix Coverage / Assumptions

The original fix (commit `15611593b3631b5a25528b9cdb2ee517ef27929a`) assumes that:
1. Blocking the `allowMultiQueries` JDBC parameter in `Mysql.java` is sufficient to prevent multi-statement execution in `previewSql`.
2. All MySQL-compatible datasource types (`mysql`, `mariadb`, `StarRocks`, `doris`, `TiDB`) parse their configuration through `Mysql.class`, inheriting the same `illegalParameters` blocklist.
3. The `previewSql` endpoint's datasource-status check (`status != "Error"`) will block any datasource that fails JDBC validation.

The fix does **not** cover:
- Other database types whose JDBC drivers support multi-statements natively without requiring a special parameter (e.g., PostgreSQL, SQL Server).
- The `previewSql` SQL-wrapping logic itself — there is still no single-statement parser or `;`/comment sanitization.
- Potential bypasses of the `illegalParameters` string-matching logic (e.g., encoding tricks).

## Variant / Alternate Trigger

### Variant 1: `mariadb` type with `allowMultiQueries=true`
- **Surface**: `POST /de2api/datasetData/previewSql` with a `mariadb` datasource configured with `allowMultiQueries=true`.
- **Code path**: `DatasetDataManage.previewSql()` → `ProviderFactory.getProvider("mariadb")` → `CalciteProvider.parseDatasourceConfiguration()` parses as `Mysql.class` → `Mysql.getJdbc()` validates `illegalParameters`.
- **Result on v2.10.20**: Datasource saved with `status="Success"`. Stacked SQL injection succeeded (confirmed by successful `INSERT INTO repro_test`).
- **Result on v2.10.21**: Datasource saved with `status="Error"` — the `allowMultiQueries` parameter was blocked by the updated `illegalParameters` list. Same root cause, same surface, blocked by the patch.

### Variant 2: Double-URL-encoded `allowMultiQueries`
- **Surface**: MySQL datasource with `extraParams=allow%254DultiQueries=true`.
- **Code path**: `Mysql.getJdbc()` → `URLDecoder.decode(jdbcUrl).toLowerCase().contains("allowmultiqueries")`.
- **Result on both versions**: The validation check is bypassed (`status="Success"`) because `URLDecoder.decode` turns `%25` into `%`, yielding `allow%4DultiQueries=true`, which does **not** match `allowmultiqueries`. However, MySQL Connector/J also performs single-level URL decoding, so the driver sees `allow%4DultiQueries=true` and does **not** enable multi-statements. The stacked SQL exploit fails. This is a validation-logic bypass without vulnerability exploitation.

### Variant 3: PostgreSQL time-based stacked query
- **Surface**: `POST /de2api/datasetData/previewSql` with a PostgreSQL datasource.
- **Code path**: `DatasetDataManage.previewSql()` builds wrapper SQL via `SQLUtils.buildOriginPreviewSql()`, translates dialect with `Provider.transSqlDialect()`, replaces placeholder with user SQL, then executes via `CalciteProvider.jdbcFetchResultField()` → `Statement.executeQuery()`.
- **Result on v2.10.20**: Baseline request took **0.03s**; stacked payload `SELECT 1) AS x; SELECT pg_sleep(5)--` took **5.04s** and returned `SQL ERROR: Multiple ResultSets were returned by the query.` The 5-second delay proves `pg_sleep(5)` executed as the second statement. This confirms the same root cause (no single-statement validation) is exploitable through PostgreSQL datasources.
- **Result on v2.10.21**: No time delay (0.03s–0.05s). The query immediately returned a PostgreSQL syntax error. The stacked query path is blocked on the fixed version, likely due to differences in how the PostgreSQL JDBC driver or Calcite SQL dialect translation handles the malformed wrapper in this build.

## Impact

- **Package/component affected**: DataEase `DatasetDataManage.previewSql()` / `CalciteProvider` — affects all database types that support multi-statement execution.
- **Affected versions**: `<= v2.10.20` (confirmed on `v2.10.20` commit `ba0052aff05d85b5ae6e81f687b777b242222dd4`).
- **Fixed versions**: `v2.10.21` (commit `e1085ffb75f42b6aca117edf36b30276bfdfe9aa`) blocks the MySQL-specific vector but does not harden the `previewSql` SQL-wrapping logic.
- **Risk level**: High (same CVSS 3.1: 8.8) for unpatched instances using PostgreSQL datasources.
- **Consequences**: Authenticated attacker can execute arbitrary stacked SQL against any datasource whose driver supports multi-statements, potentially affecting tables, permissions, or application state.

## Root Cause

The root cause is identical to CVE-2026-40900:
1. `DatasetDataManage.previewSql()` wraps raw user SQL in a subquery (`SELECT * FROM ( <USER_SQL> ) tmp LIMIT 100`) without parsing or validating that the input is a single `SELECT` statement.
2. When the underlying JDBC driver supports multi-statement execution, the attacker can inject `); <SECOND_STATEMENT> <COMMENT>` to break out of the wrapper and execute arbitrary side-effecting SQL.

The fix only addresses the **prerequisite** (`allowMultiQueries=true` in MySQL) rather than the **root cause** (missing single-statement validation). Other database types (e.g., PostgreSQL) whose drivers support multi-statements by default remain potentially vulnerable on unpatched versions.

## Reproduction Steps

See `vuln_variant/reproduction_steps.sh` for the automated end-to-end reproduction.

At a high level:
1. Start DataEase `v2.10.20` + MySQL + PostgreSQL via Docker Compose.
2. Log in as `admin` / `DataEase@123456` (RSA-encrypted credentials fetched via `/de2api/dekey`).
3. Create a PostgreSQL datasource (`type=pg`, host `pg`, port `5432`, database `dataease`).
4. Send `POST /de2api/datasetData/previewSql` with a base64-encoded stacked-SQL payload:
   ```
   SELECT 1) AS x; SELECT pg_sleep(5)--
   ```
5. Observe the response takes ~5 seconds (baseline is <0.1s), confirming the second statement executed.
6. On v2.10.21, repeat the same request and observe immediate failure with no time delay, confirming the variant is blocked.

## Evidence

- `logs/variant3_v2.10.20_baseline.json` — baseline `previewSql` response against PostgreSQL (elapsed: 0.03s, code: 0).
- `logs/variant3_v2.10.20_exploit.json` — stacked `pg_sleep(5)` payload response (elapsed: 5.04s, error: "Multiple ResultSets were returned by the query.").
- `logs/variant3_v2.10.21_exploit.json` — same payload on fixed version (elapsed: 0.03s, error: PostgreSQL syntax error).
- `logs/variant1_v2.10.20_create.json` — `mariadb` datasource with `allowMultiQueries=true` saved as `status=Success`.
- `logs/variant1_v2.10.20_exploit.json` — successful stacked SQL injection through `mariadb` datasource.
- `logs/variant2_v2.10.21_create.json` — MySQL datasource with `allow%254DultiQueries=true` saved as `status=Success` (validation bypassed).

## Recommendations / Next Steps

1. **Add server-side SQL-structure validation in `previewSql`** (defense-in-depth). Before wrapping user SQL, parse it with a SQL parser (e.g., JSqlParser or Calcite `SqlParser`) and reject any input that:
   - Contains more than one top-level statement (`;` outside quotes/comments).
   - Contains comment sequences (`--`, `/*`, `#`, etc.) that could swallow the wrapper suffix.
   - Is not a `SELECT` statement.

2. **Extend `illegalParameters` validation robustness**:
   - Perform iterative URL decoding (up to 3–5 rounds) before matching, or use a whitelist approach for allowed parameters instead of a blacklist.
   - Add `Normalizer.normalize()` (NFKC) to defeat Unicode homoglyph attacks.

3. **Audit other datasource types** for multi-statement support. PostgreSQL and SQL Server drivers support multi-statements natively. Consider adding driver-specific restrictions or, better, adding the universal SQL-structure validation described above.

4. **Regression test**: Include stacked-SQL payloads for multiple database types (MySQL, PostgreSQL, SQL Server) in the CI pipeline for `previewSql`.

## Additional Notes

- **Idempotency**: The script was run multiple times (8 iterations) with consistent results. Cleanup removes Docker volumes and containers via `docker compose down -v` and `docker volume rm`.
- **Limitations**: Variant 2 bypasses the `illegalParameters` check but does not yield a working exploit because MySQL Connector/J does not double-decode `%254D`. A custom driver or future driver change could potentially close this gap.
- **Trust boundary**: All variants require authenticated access to the DataEase `previewSql` endpoint (same as the original CVE).
