# REPRO-2026-00276: Apache Gravitino unauthenticated H2 JDBC URL injection via testConnection API ## Summary Status: published Severity: low Type: security Confidence: high ## Identifiers REPRO ID: REPRO-2026-00276 CVE: CVE-2026-41042 ## Package Name: apache/gravitino Ecosystem: github Affected: Apache Gravitino before 1.2.1 Fixed: 1.2.1 ## Root Cause # Root Cause Analysis — CVE-2026-41042 ## Summary Apache Gravitino (before 1.2.1) exposes an unauthenticated REST endpoint `POST /api/metalakes/{metalake}/catalogs/testConnection` that lets a caller supply an arbitrary JDBC URL for a catalog connection test. Because no validation rejected H2 JDBC URLs or the H2 driver class, an attacker could supply a malicious `jdbc:h2:...;INIT=...` URL. H2's `INIT` connection parameter runs arbitrary SQL when the database is first opened, and H2's `CREATE ALIAS ... AS ''` compiles and executes arbitrary Java code inside the Gravitino server JVM. By combining `CREATE ALIAS` with `Runtime`/`ProcessBuilder` calls, an unauthenticated remote caller achieves arbitrary operating-system command execution on the server. ## Impact - **Package/component affected:** `org.apache.gravitino:gravitino-catalog-jdbc-common` (`DataSourceUtils.createDataSource`), reached through the `testConnection` REST endpoint and the JDBC catalog operations (`JdbcCatalogOperations.initialize`). - **Affected versions:** Apache Gravitino before 1.2.1 (confirmed on the official `1.2.0` binary distribution; H2 `1.4.200` is bundled). - **Risk level / consequences:** Remote code execution. The default server configuration uses the `SimpleAuthenticator` (accepts anonymous requests) and has `gravitino.authorization.enable = false`, so the endpoint is reachable without any credentials. The H2 driver is always on the server classpath (it is the default entity-store backend) and is treated as a "shared" class by the catalog `IsolatedClassLoader`, so attacker-supplied H2 URLs resolve to the bundled driver even for non-H2 catalog providers. ## Impact Parity - **Disclosed/claimed maximum impact:** Remote code execution — "executes arbitrary Java code on the server via H2's INIT parameter", expected impact `code_execution`. - **Reproduced impact from this run:** Arbitrary OS command execution confirmed end-to-end through the unauthenticated REST API. The malicious `testConnection` call returned HTTP 200 / `{"code":0}` and a marker file containing the output of the `id` command (`uid=1000(vscode) gid=1000(vscode) groups=1000(vscode)`) was written by a `ProcessBuilder` started from H2-compiled Java code running in the server JVM. - **Parity:** `full`. - **Not demonstrated:** N/A — the full code-execution chain was demonstrated. ## Root Cause `DataSourceUtils.createDataSource(JdbcConfig)` is the single entry point for all user-facing catalog JDBC connections. In the vulnerable version it performed no allow-listing of JDBC drivers/URLs: it passed the caller- controlled `jdbc-url` and `jdbc-driver` straight into a DBCP `BasicDataSource`. The shared validator `JdbcUrlUtils.validateJdbcConfig` only inspects MySQL/MariaDB/PostgreSQL URLs for unsafe parameters and intentionally does **not** touch H2 (H2 is the legitimate embedded entity-store backend). Consequently an H2 URL reached the driver untouched. The call chain triggered by `testConnection` is: ``` CatalogOperations.testConnection (REST, @Path "testConnection") -> CatalogManager.testConnection -> CatalogWrapper.doWithCatalogOps -> BaseCatalog.ops() (lazy initialize) -> JdbcCatalogOperations.initialize(conf, ...) -> DataSourceUtils.createDataSource(jdbcConfig) <-- vulnerable -> BasicDataSource (url=jdbc:h2:mem:...;INIT=..., driver=org.h2.Driver) ``` When the pool opens its first connection (during `JdbcCatalogOperations.testConnection -> databaseOperation.listDatabases()`), H2 processes the `INIT` parameter. The payload `INIT=CREATE ALIAS EXEC AS 'String f() throws Exception { new ProcessBuilder(...).start().waitFor(); ... }'\;CALL EXEC()` compiles the Java source via the JDK compiler and invokes it, executing the attacker's OS command inside the server process. **Fix commit:** `5daabcd0e8ddc96e25bd6c6ce7b153cdb311c3f2` ("[MINOR] fix(catalog): block H2 JDBC URL and driver in catalog datasource creation (#10801)"), cherry-picked to the 1.2 release branch as `84d3de9c7` and shipped in Gravitino 1.2.1. The fix adds a case-insensitive block in `DataSourceUtils.createDataSource()` that rejects any URL whose decoded form starts with `jdbc:h2` and any driver class starting with `org.h2.`, throwing `GravitinoRuntimeException` before the datasource is created. ## Reproduction Steps 1. **Script:** `bundle/repro/reproduction_steps.sh` (self-contained, idempotent, run twice consecutively — both runs pass with exit 0). 2. **What it does:** - Installs OpenJDK 17 if needed. - Downloads/reuses the official Apache Gravitino binary distributions `gravitino-1.2.0-bin.tar.gz` (vulnerable) and `gravitino-1.2.1-bin.tar.gz` (fixed) from the GitHub release CDN (fallback: Apache archive). - For each version: extracts a clean copy, disables auxiliary services, starts the real `gravitino.sh` server, waits for the `/api/version` healthcheck, creates a metalake, and sends an **unauthenticated** `POST /api/metalakes/test_ml/catalogs/testConnection` with `provider=jdbc-postgresql`, `jdbc-driver=org.h2.Driver`, and a malicious `jdbc-url` whose `INIT` runs `CREATE ALIAS` + `ProcessBuilder` to execute `id` and write its output to a marker file. Two attempts are made per version (each attempt uses a fresh H2 in-memory DB name because H2 only runs `INIT` when a database is first created). - Captures per-attempt request/response logs, server logs, version JSON, and the RCE marker file under `bundle/logs/`. - Writes `bundle/repro/runtime_manifest.json`. 3. **Expected evidence:** - Vulnerable 1.2.0: `testConnection` returns HTTP 200 `{"code":0}` and the marker file is created containing `id` output → RCE. - Fixed 1.2.1: `testConnection` returns HTTP 500 `"H2 JDBC URL is not allowed in catalog configuration"` (thrown at `DataSourceUtils.createDataSource:54`) and no marker file is created. ## Evidence - `bundle/logs/reproduction_steps.log` — orchestrator log with verdict. - `bundle/logs/vuln_version.json` — `1.2.0`, gitCommit `1c1665f0...`. - `bundle/logs/fixed_version.json` — `1.2.1`, gitCommit `e88bffcfc...`. - `bundle/logs/vuln_attempt_1.log` / `vuln_attempt_2.log` — `testConnection_status: 200`, `testConnection_body: {"code":0}`, `marker_exists: True`, `marker_content: uid=1000(vscode) ...`. - `bundle/logs/rce_marker_vuln_latest.txt` — the file written by the attacker's OS command inside the server JVM. - `bundle/logs/fixed_attempt_1.log` / `fixed_attempt_2.log` — `testConnection_status: 500`, message `"H2 JDBC URL is not allowed in catalog configuration"`, stack at `DataSourceUtils.createDataSource(DataSourceUtils.java:54)`, `marker_exists: False`. - `bundle/logs/vuln_server.log` / `fixed_server.log` — server-side logs. - `bundle/repro/runtime_manifest.json` — runtime evidence manifest. Key excerpts: ``` # Vulnerable 1.2.0 (unauthenticated) vuln_a1 testConnection_status: 200 vuln_a1 testConnection_body: {"code":0} vuln_a1 marker_exists: True vuln_a1 marker_content: uid=1000(vscode) gid=1000(vscode) groups=1000(vscode) # Fixed 1.2.1 (unauthenticated) fixed_a1 testConnection_status: 500 fixed_a1 testConnection_body: {"code":1002,"type":"RuntimeException", "message":"H2 JDBC URL is not allowed in catalog configuration", "stack":["...DataSourceUtils.createDataSource(DataSourceUtils.java:54)", "...JdbcCatalogOperations.initialize(JdbcCatalogOperations.java:175)",...]} fixed_a1 marker_exists: False ``` Environment: OpenJDK 17.0.19, official Apache Gravitino binary tarballs, H2 1.4.200 (bundled), Jetty 9, commons-dbcp2 2.11.0, default server config (SimpleAuthenticator, authorization disabled, H2 entity-store backend). ## Recommendations / Next Steps - **Upgrade** to Apache Gravitino 1.2.1 or later, which blocks H2 JDBC URLs and the `org.h2.` driver class in `DataSourceUtils.createDataSource()`. - Consider a positive allow-list of permitted JDBC URL schemes/drivers in catalog configuration rather than only blocking H2, and apply the same check at catalog *creation* time (not only `testConnection`). - In production deployments, enable authentication/authorization and use MySQL (not H2) for the entity store; restrict network exposure of the `testConnection` endpoint. - Add regression tests that assert a `jdbc:h2:...;INIT=...` URL and `org.h2.Driver` are rejected through the full REST `testConnection` path. ## Additional Notes - **Idempotency:** confirmed — the script was run twice consecutively; both runs exited 0 with identical verdicts (vulnerable RCE confirmed on both attempts, fixed blocked on both attempts). Each run extracts into fresh directories and uses a unique H2 in-memory DB name per attempt. - **Payload detail:** every `;` inside the H2 `INIT` value (including Java statement terminators) must be escaped as `\;`, because H2 uses `;` as the URL parameter separator; unescaped `;` truncates the URL and the INIT never runs. The `jdbc-database` property must also be supplied for the `jdbc-postgresql` provider (`PostgreSqlSchemaOperations.initialize` reads it before the test connection is opened). - The exploit does not require the catalog provider to be H2-aware: the H2 driver is a "shared" class (`!isCatalogClass`) so the catalog `IsolatedClassLoader` delegates `org.h2.Driver` to the server classloader, which always has it. ## Reproduction Details Reproduced: 2026-07-09T14:28:23.013Z Duration: 1367 seconds Tool calls: 308 Turns: Unknown Handoffs: 3 ## Quick Verification Run one of these commands to verify locally: pruva-verify REPRO-2026-00276 pruva-verify CVE-2026-41042 Or open in GitHub Codespaces (zero-friction, auto-runs): https://github.com/codespaces/new?ref=repro/REPRO-2026-00276&repo=N3mes1s/pruva-sandbox Or download and run the script manually: curl -O https://api.pruva.dev/v1/reproductions/REPRO-2026-00276/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-41042 - Source: https://github.com/apache/gravitino ## Artifacts - bundle/repro/reproduction_steps.sh (reproduction_script, 12713 bytes) - bundle/repro/rca_report.md (analysis, 9508 bytes) - bundle/vuln_variant/reproduction_steps.sh (reproduction_script, 12241 bytes) - bundle/vuln_variant/rca_report.md (analysis, 15021 bytes) - bundle/coding/proposed_fix.diff (patch, 5054 bytes) - bundle/artifact_promotion_manifest.json (other, 13180 bytes) - bundle/artifact_promotion_report.json (other, 18135 bytes) - bundle/vuln_variant/source_identity.json (other, 2369 bytes) - bundle/vuln_variant/root_cause_equivalence.json (other, 3067 bytes) - bundle/coding/H2BlockTest.java (other, 4825 bytes) - bundle/coding/PaimonH2BlockTest.java (other, 4915 bytes) - bundle/repro/validation_verdict.json (other, 1024 bytes) - bundle/repro/runtime_manifest.json (other, 2008 bytes) - bundle/logs/rce_marker_vuln_latest.txt (other, 54 bytes) - bundle/logs/vuln_attempt_1.log (log, 331 bytes) - bundle/logs/fixed_attempt_1.log (log, 648 bytes) - bundle/logs/reproduction_steps.log (log, 1590 bytes) - bundle/logs/vuln_version.json (other, 131 bytes) - bundle/logs/vuln_attempt_2.log (log, 331 bytes) - bundle/logs/vuln_server.log (log, 34056 bytes) - bundle/logs/fixed_version.json (other, 131 bytes) - bundle/logs/fixed_attempt_2.log (log, 648 bytes) - bundle/logs/fixed_server.log (log, 33436 bytes) - bundle/logs/vuln_variant/rce_marker_paimon_fixed_latest.txt (other, 54 bytes) - bundle/logs/vuln_variant/paimon_fixed_server.log (log, 62690 bytes) - bundle/vuln_variant/validation_verdict.json (other, 2614 bytes) - bundle/vuln_variant/variant_manifest.json (other, 5471 bytes) - bundle/vuln_variant/patch_analysis.md (documentation, 9117 bytes) - bundle/vuln_variant/runtime_manifest.json (other, 3949 bytes) - bundle/coding/verify_fix.sh (other, 5581 bytes) - bundle/coding/summary_report.md (documentation, 9611 bytes) ## API Access - JSON: https://api.pruva.dev/v1/reproductions/REPRO-2026-00276 - Script: https://api.pruva.dev/v1/reproductions/REPRO-2026-00276/artifacts/bundle/repro/reproduction_steps.sh - Web: https://pruva.dev/r/REPRO-2026-00276 ## 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