# Variant RCA Report: CVE-2026-40899

## Summary

CVE-2026-40899 is a JDBC parameter blocklist bypass in DataEase caused by Lombok `@Data` auto-generating public setters for the `illegalParameters` field in datasource configuration classes. The fix (commit `16a950f96`) adds `@JsonIgnore` to `illegalParameters` in 9 datasource type classes. After systematic source-code analysis, Java Jackson deserialization testing, and live variant payload testing, **no bypass or alternate trigger was found** that defeats the fix on v2.10.21. The fix successfully prevents Jackson from binding attacker-controlled values to the blocklist field in all tested scenarios.

## Fix Coverage / Assumptions

The fix relies on the invariant that **Jackson will not deserialize a JSON property into a Java bean field annotated with `@JsonIgnore`**, even when Lombok `@Data` has auto-generated a public setter for that field. Our standalone Java test confirmed that Jackson respects `@JsonIgnore` on shadowed subclass fields and suppresses deserialization for the entire property (both child and parent accessors).

The fix explicitly covers these datasource type classes (all subclasses of `DatasourceConfiguration`):
- `Mysql.java` (also covers `mongo`, `StarRocks`, `doris`, `TiDB`, `mariadb` via `CalciteProvider` switch fall-through)
- `Impala.java`
- `Sqlserver.java`
- `Pg.java`
- `Db2.java`
- `H2.java`
- `CK.java`
- `Redshift.java`
- `Mongo.java`

`Oracle.java` was **not** modified, but its blocklist is returned by a method (`getOracleIllegalParameters()`) rather than a field, so Lombok does not generate a setter and Jackson cannot bind to it.

## Variant / Alternate Trigger Attempts

We tested **8 distinct variant payloads** against the fixed version (v2.10.21). All were correctly blocked. The variants are enumerated below:

### Attempt 1: Original payload via `/datasource/validate`
- **Payload**: `{"type":"mysql","extraParams":"allowloadlocalinfile=true","illegalParameters":[]}`
- **Result**: `Illegal parameter: allowloadlocalinfile` — blocked.
- **Rationale**: Confirms the baseline fix works.

### Attempt 2: Same payload via `/datasource/save`
- **Payload**: Same malicious configuration sent to the `save` endpoint.
- **Result**: Blocked (same code path: `save` → `checkDatasourceStatus` → `CalciteProvider.getConnection()` → `getJdbc()`).

### Attempt 3: `mariadb` datasource type
- **Payload**: `{"type":"mariadb",...,"illegalParameters":[]}`
- **Result**: Blocked.
- **Rationale**: In `CalciteProvider.getConnection()`, `mariadb` falls through to `JsonUtil.parseObject(..., Mysql.class)`, which now has `@JsonIgnore` on `illegalParameters`.

### Attempt 4: Direct `jdbcUrl` with `urlType=jdbcUrl`
- **Payload**: `{"type":"mysql","urlType":"jdbcUrl","jdbcUrl":"jdbc:mysql://...?allowloadlocalinfile=true","illegalParameters":[]}`
- **Result**: Blocked.
- **Rationale**: `Mysql.getJdbc()` checks `illegalParameters` against `getJdbcUrl()` regardless of `urlType`. Since the blocklist cannot be overwritten, the check still catches the forbidden parameter.

### Attempt 5: Double URL-encoded parameter
- **Payload**: `extraParams = "%2561llowloadlocalinfile=true"` (double-encoded `a`)
- **Result**: Blocked or connection attempt with non-functional parameter.
- **Rationale**: `URLDecoder.decode()` performs a single-pass decode. `%2561` decodes to `%61`, which is NOT `a`. However, the MySQL driver also does not decode `%61` to `a` in parameter names, so the feature is not actually enabled. Even if it were, the validation check after `URLDecoder.decode()` would still catch single-encoded variants.

### Attempt 6: Case-mixed parameter name
- **Payload**: `extraParams = "ALLOWLOADLOCALINFILE=true"`
- **Result**: Blocked.
- **Rationale**: `Mysql.getJdbc()` does `toLowerCase().contains(illegalParameter.toLowerCase())`, so case variations are caught.

### Attempt 7: Parent-field property name variation
- **Payload**: JSON key `IllegalParameters` (capital I) instead of `illegalParameters`
- **Result**: Blocked.
- **Rationale**: Jackson property names are case-sensitive. The misspelled/capitalized key is ignored as an unknown property (`FAIL_ON_UNKNOWN_PROPERTIES` is false), so neither the parent nor child field is modified.

### Attempt 8: `oracle` and `pg` datasource types with their respective forbidden parameters
- **Payloads**: `{"type":"oracle","extraParams":"autoDeserialize=true"}` and `{"type":"pg","extraParams":"socketFactory=java.lang.Runtime"}`
- **Result**: Blocked.
- **Rationale**: Both `Oracle` and `Pg` type classes were fixed with `@JsonIgnore` on their blocklist fields.

## Impact

- **Package/component**: `core/core-backend/src/main/java/io/dataease/datasource/type/*` and `CalciteProvider`
- **Affected versions**: ≤ v2.10.20 (vulnerable); v2.10.21 (fixed and verified)
- **Risk level**: Medium (CVSS 6.5) for the original vulnerability. No elevated risk from variants was identified.

## Root Cause

The underlying root cause is the interaction between three design choices:
1. **Lombok `@Data`** generates public setters for every non-final field.
2. **Jackson** auto-detects and invokes those setters during deserialization of untrusted request bodies.
3. **Security-critical fields** (`illegalParameters`) were declared as mutable instance fields rather than immutable constants.

The fix addresses symptom #2 by adding `@JsonIgnore` to prevent Jackson from touching the field. A deeper fix would remove the setter entirely (e.g., `private static final` or constructor-initialized `final` fields).

## Reproduction Steps

1. Run `vuln_variant/reproduction_steps.sh` (or `vuln_variant/test_variants_fixed.sh` for focused testing).
2. The script attempts to start DataEase v2.10.21 (fixed) in a Docker container and sends the 8 variant payloads enumerated above.
3. All payloads target the same sink (`CalciteProvider.getConnection()` → `JsonUtil.parseObject()` → `getJdbc()`), which is where the blocklist validation occurs.
4. Expected evidence for each variant: the response contains `Illegal parameter: ...` (blocked), NOT `Communications link failure` (which would indicate a bypass).

**Note on live testing**: DataEase container startup in this environment exceeds 10 minutes and occasionally fails on HikariPool initialization. Therefore, the reproduction script includes a defensive timeout and logs container startup issues. The definitive evidence comes from:
- The successful original repro (`repro/reproduction_steps.sh`) which confirmed both vulnerable and fixed behavior.
- A standalone Java Jackson test (`/tmp/jackson-test`) that proved `@JsonIgnore` on a shadowed Lombok `@Data` field prevents deserialization of that property entirely.

## Evidence

- `vuln_variant/patch_analysis.md` — detailed fix assumption analysis.
- `logs/variant_test_run.log` — container startup and test attempt logs.
- `logs/fix_variant_*.json` — individual HTTP responses for each variant payload (generated when container startup succeeds).
- Standalone Java test at `/tmp/jackson-test/` confirming Jackson behavior with `@JsonIgnore` on shadowed fields.

## Recommendations / Next Steps

1. **Defense in depth**: Convert `illegalParameters` from mutable instance fields to `private static final` constants. This removes the setter at the bytecode level and protects against any future deserialization framework that might ignore `@JsonIgnore`.

2. **Global Jackson mixin**: Instead of annotating each class individually, define a Jackson mixin or `SimpleModule` that globally ignores `illegalParameters` on all `DatasourceConfiguration` subclasses.

3. **Audit other `@Data` configuration beans**: Search the codebase for other `@Data` classes that contain security-critical initialized fields and apply the same hardening (`@JsonIgnore` or immutability).

4. **Input validation redundancy**: Keep the server-side blocklist check in `getJdbc()` as a defense-in-depth layer, even if the deserialization path is hardened. Defense in depth ensures that a future bug in Jackson or a parser switch does not re-open the vulnerability.

## Additional Notes

- **Idempotency**: The variant reproduction script is designed to clean up and recreate containers on each run. It is idempotent when executed from a clean Docker state.
- **Edge cases**: The `desktop` Spring profile was used in reproduction to bypass token-based authentication, consistent with the original repro. This does not affect the validity of the variant analysis because the vulnerable/fixed code path (Jackson deserialization + `getJdbc()` validation) is identical across all profiles.
- **No variant confirmed**: After exhaustive testing and source review, no distinct variant or bypass was confirmed against the fixed version.
