# REPRO-2026-00169: DataEase: stacked-query SQL injection via previewSql with allowMultiQueries ## Summary Status: published Severity: high Type: security Confidence: Unknown ## Identifiers REPRO ID: REPRO-2026-00169 CVE: CVE-2026-40900 ## Package Name: dataease Ecosystem: github Affected: <= v2.10.20 Fixed: v2.10.21 ## Root Cause # 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 ( ) 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. ## Reproduction Details Reproduced: 2026-05-26T00:25:43.961Z Duration: 3510 seconds Tool calls: 582 Turns: 510 Handoffs: 2 ## Quick Verification Run one of these commands to verify locally: pruva-verify REPRO-2026-00169 pruva-verify CVE-2026-40900 Or open in GitHub Codespaces (zero-friction, auto-runs): https://github.com/codespaces/new?ref=repro/REPRO-2026-00169&repo=N3mes1s/pruva-sandbox Or download and run the script manually: curl -O https://api.pruva.dev/v1/reproductions/REPRO-2026-00169/artifacts/bundle/repro/reproduction_steps.sh chmod +x reproduction_steps.sh ./reproduction_steps.sh WARNING: Run in a sandboxed environment. This exploits a real vulnerability. ## References - NVD: https://nvd.nist.gov/vuln/detail/CVE-2026-40900 - Source: https://github.com/dataease/dataease ## Artifacts - bundle/repro/rca_report.md (analysis, 5670 bytes) - bundle/repro/reproduction_steps.sh (reproduction_script, 12221 bytes) - bundle/vuln_variant/rca_report.md (analysis, 9150 bytes) - bundle/vuln_variant/reproduction_steps.sh (reproduction_script, 12551 bytes) - bundle/context.json (other, 4850 bytes) - bundle/metadata.json (other, 845 bytes) - bundle/ticket.md (ticket, 7315 bytes) - bundle/repro/my.cnf (other, 94 bytes) - bundle/repro/init.sql (other, 99 bytes) - bundle/repro/patch_analysis.md (documentation, 3220 bytes) - bundle/repro/docker-compose.yml (other, 1029 bytes) - bundle/repro/application.yml (other, 612 bytes) - bundle/repro/validation_verdict.json (other, 1550 bytes) - bundle/vuln_variant/root_cause_equivalence.json (other, 1609 bytes) - bundle/vuln_variant/patch_analysis.md (documentation, 4159 bytes) - bundle/vuln_variant/docker-compose.yml (other, 1404 bytes) - bundle/vuln_variant/variant_manifest.json (other, 3260 bytes) - bundle/vuln_variant/runtime_manifest.json (other, 1065 bytes) - bundle/vuln_variant/validation_verdict.json (other, 4084 bytes) - bundle/vuln_variant/source_identity.json (other, 783 bytes) - bundle/logs/variant3_v2.10.21_create.json (other, 925 bytes) - bundle/logs/variant1_v2.10.20_exploit.json (other, 869 bytes) - bundle/logs/variant3_v2.10.21_baseline.json (other, 822 bytes) - bundle/logs/variant2_v2.10.20_exploit.json (other, 330 bytes) - bundle/logs/variant_run.log (log, 4287 bytes) - bundle/logs/variant_run7.log (log, 2770 bytes) - bundle/logs/variant3_v2.10.20_exploit.json (other, 151 bytes) - bundle/logs/variant3_v2.10.21_exploit.json (other, 161 bytes) - bundle/logs/variant_run5.log (log, 917 bytes) - bundle/logs/variant_run4.log (log, 936 bytes) - bundle/logs/v2.10.20_validate.json (other, 566 bytes) - bundle/logs/variant3_v2.10.20_baseline.json (other, 822 bytes) - bundle/logs/variant_run3.log (log, 824 bytes) - bundle/logs/variant2_v2.10.21_exploit.json (other, 330 bytes) - bundle/logs/variant_run2.log (log, 825 bytes) - bundle/logs/v2.10.21_create_datasource.json (other, 928 bytes) - bundle/logs/variant1_v2.10.20_create.json (other, 944 bytes) - bundle/logs/variant_v2.10.21_docker.log (log, 576 bytes) - bundle/logs/v2.10.21_exploit.json (other, 51 bytes) - bundle/logs/variant2_v2.10.20_create.json (other, 942 bytes) - bundle/logs/variant_v2.10.20_docker.log (log, 576 bytes) - bundle/logs/variant2_v2.10.21_create.json (other, 942 bytes) - bundle/logs/v2.10.21_validate.json (other, 100 bytes) - bundle/logs/variant1_v2.10.21_create.json (other, 942 bytes) - bundle/logs/v2.10.20_exploit.json (other, 762 bytes) - bundle/logs/variant3_v2.10.20_create.json (other, 925 bytes) - bundle/logs/variant_run6.log (log, 2746 bytes) - bundle/logs/variant_run8.log (log, 2839 bytes) ## API Access - JSON: https://api.pruva.dev/v1/reproductions/REPRO-2026-00169 - Script: https://api.pruva.dev/v1/reproductions/REPRO-2026-00169/artifacts/bundle/repro/reproduction_steps.sh - Web: https://pruva.dev/r/REPRO-2026-00169 ## For AI Assistants To help users with this reproduction: 1. The `pruva-verify` command is the simplest way to run the reproduction 2. The script downloads and runs automatically in an isolated temp directory 3. Always recommend running in a VM, container, or disposable environment 4. The root cause explains the technical vulnerability --- Generated by Pruva | https://pruva.dev