CVE-2026-41042: Verified Reproduction
CVE-2026-41042: Apache Gravitino unauthenticated H2 JDBC URL injection via testConnection API
CVE-2026-41042 is verified against apache/gravitino · github. Affected versions: Apache Gravitino before 1.2.1. Fixed in 1.2.1. This critical reproduction includes runnable sandbox proof, artifacts, and a plain-text agent view under REPRO-2026-00276.
What Is CVE-2026-41042?
CVE-2026-41042 is a low-severity vulnerability in Apache Gravitino before 1.2.1 in which an unauthenticated caller can supply a malicious H2 JDBC URL through the testConnection API (or catalog creation) that executes arbitrary Java code on the server via H2's INIT parameter. Pruva reproduced it (reproduction REPRO-2026-00276).
CVE-2026-41042 Severity & CVSS Score
CVE-2026-41042 is rated critical severity, with a CVSS base score of 9.1 out of 10.
Critical — the most severe class — typically remotely exploitable with severe impact. Treat as an emergency.
Affected apache/gravitino Versions
apache/gravitino · github versions Apache Gravitino before 1.2.1 are affected.
How to Reproduce CVE-2026-41042
pruva-verify REPRO-2026-00276 curl -O https://www.pruva.dev/api/v1/reproductions/REPRO-2026-00276/artifacts/bundle/repro/reproduction_steps.sh && chmod +x reproduction_steps.sh && ./reproduction_steps.sh Proof of Reproduction for CVE-2026-41042
- reached the target end-to-end
- full exploit chain demonstrated
- on the real production code path
- high confidence
- the upstream fix blocks the same trigger
malicious H2 JDBC URL (jdbc:h2:mem:...;INIT=CREATE ALIAS ... ProcessBuilder ...) supplied unauthenticated via testConnection API catalog properties
- POST /api/metalakes/{metalake}/catalogs/testConnection
- CatalogManager.testConnection
- BaseCatalog.ops()
- JdbcCatalogOperations.initialize
- DataSourceUtils.createDataSource
- H2 driver opens connection and runs INIT
- CREATE ALIAS compiles+executes Java
- ProcessBuilder runs OS command
reproduction_steps.sh How the agent worked
Root Cause and Exploit Chain for CVE-2026-41042
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 '<java source>' 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.
- Package/component affected:
org.apache.gravitino:gravitino-catalog-jdbc-common(DataSourceUtils.createDataSource), reached through thetestConnectionREST endpoint and the JDBC catalog operations (JdbcCatalogOperations.initialize). - Affected versions: Apache Gravitino before 1.2.1 (confirmed on the
official
1.2.0binary distribution; H21.4.200is bundled). - Risk level / consequences: Remote code execution. The default server
configuration uses the
SimpleAuthenticator(accepts anonymous requests) and hasgravitino.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 catalogIsolatedClassLoader, 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
testConnectioncall returned HTTP 200 /{"code":0}and a marker file containing the output of theidcommand (uid=1000(vscode) gid=1000(vscode) groups=1000(vscode)) was written by aProcessBuilderstarted 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
- Script:
bundle/repro/reproduction_steps.sh(self-contained, idempotent, run twice consecutively — both runs pass with exit 0). - What it does:
- Installs OpenJDK 17 if needed.
- Downloads/reuses the official Apache Gravitino binary distributions
gravitino-1.2.0-bin.tar.gz(vulnerable) andgravitino-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.shserver, waits for the/api/versionhealthcheck, creates a metalake, and sends an unauthenticatedPOST /api/metalakes/test_ml/catalogs/testConnectionwithprovider=jdbc-postgresql,jdbc-driver=org.h2.Driver, and a maliciousjdbc-urlwhoseINITrunsCREATE ALIAS+ProcessBuilderto executeidand 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 runsINITwhen 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.
- Expected evidence:
- Vulnerable 1.2.0:
testConnectionreturns HTTP 200{"code":0}and the marker file is created containingidoutput → RCE. - Fixed 1.2.1:
testConnectionreturns HTTP 500"H2 JDBC URL is not allowed in catalog configuration"(thrown atDataSourceUtils.createDataSource:54) and no marker file is created.
- Vulnerable 1.2.0:
Evidence
bundle/logs/reproduction_steps.log— orchestrator log with verdict.bundle/logs/vuln_version.json—1.2.0, gitCommit1c1665f0....bundle/logs/fixed_version.json—1.2.1, gitCommite88bffcfc....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 atDataSourceUtils.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 inDataSourceUtils.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
testConnectionendpoint. - Add regression tests that assert a
jdbc:h2:...;INIT=...URL andorg.h2.Driverare rejected through the full RESTtestConnectionpath.
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 H2INITvalue (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. Thejdbc-databaseproperty must also be supplied for thejdbc-postgresqlprovider (PostgreSqlSchemaOperations.initializereads 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 catalogIsolatedClassLoaderdelegatesorg.h2.Driverto the server classloader, which always has it.
CVE-2026-41042 Reproduction Transcript
The agent's step-by-step process — every tool call, every handoff, the moment the exploit fired.
Full session Replay every step — scrub the timeline or play it back.
Artifacts and Evidence for CVE-2026-41042
Scripts, logs, diffs, and output captured during the reproduction.
How to Fix CVE-2026-41042
Upgrade apache/gravitino · github to 1.2.1 or later.
FAQ: CVE-2026-41042
How does the CVE-2026-41042 exploit work?
Which Apache Gravitino versions are affected by CVE-2026-41042, and where is it fixed?
How severe is CVE-2026-41042?
How can I reproduce CVE-2026-41042?
References for CVE-2026-41042
Authoritative sources for CVE-2026-41042 — official vulnerability databases and the upstream advisory. Pruva's reproduction verifies the issue firsthand; these are the primary records to corroborate it.