# REPRO-2026-00293: Metabase arbitrary code execution via unsafe H2 connection property validation bypass ## Summary Status: published Severity: critical Type: security Confidence: high ## Identifiers REPRO ID: REPRO-2026-00293 CVE: CVE-2026-59826 ## Package Name: metabase/metabase Ecosystem: github Affected: Unknown Fixed: Unknown ## Root Cause ## Summary Metabase Enterprise allowed an authenticated administrator to persist unsafe H2 database connection details through the Enterprise database-routing destination database creation API. In vulnerable Metabase v1.61.1, `POST /api/ee/database-routing/destination-database` created an H2 destination database without first invoking the H2 driver's unsafe-detail validation. A crafted H2 connection string using an escaped semicolon inside a non-`INIT` connection property was persisted, and a later product query through `POST /api/dataset` opened that destination connection and executed attacker-controlled H2 SQL inside the Metabase JVM. The fixed version v1.61.2 rejects the same destination details before persistence/execution. ## Impact - Package/component affected: Metabase Enterprise, specifically the database-routing API endpoint `POST /api/ee/database-routing/destination-database` and H2 driver connection-detail validation. - Affected versions: reported as `>= 1.55.0 < 1.58.15.1`, `>= 1.59.0 < 1.59.12`, `>= 1.60.0 < 1.60.6.3`, and `>= 1.61.0 < 1.61.2`. - Risk level and consequences: critical. An authenticated administrator can create a crafted H2 destination database, then cause Metabase to open it. H2 executes SQL embedded in connection properties, which can be escalated to arbitrary Java/code execution primitives supported by H2. The reproduction demonstrates server-side attacker-controlled file creation from inside the Metabase JVM. ## Impact Parity - Disclosed/claimed maximum impact: arbitrary code execution / arbitrary Java code execution on the Metabase server by an authenticated administrator. - Reproduced impact from this run: the real Metabase Enterprise product accepted a crafted H2 destination through the remote API, later opened the persisted connection through the `/api/dataset` product endpoint, and H2 executed attacker-controlled SQL that wrote marker files inside the Metabase container in two independent vulnerable attempts. - Parity: `full`. - Not demonstrated: the proof stops at deterministic server-side H2 SQL execution/file-write markers rather than spawning an OS shell. This is the same unsafe H2 execution mechanism underlying the code-execution claim; the fixed-version negative control demonstrates the intended validation blocks this primitive before persistence. ## Root Cause The vulnerable endpoint persisted database-routing destinations without validating details that were unsafe to store. The standard H2 connection path already had logic to reject unsupported H2 data-warehouse use and dangerous connection properties during connection testing, and the H2 connection builder stripped URL options whose key was `INIT`. However, `POST /api/ee/database-routing/destination-database` was designed not to test reachability at creation time, and in the vulnerable version it also skipped an unconditional validation pass over details before insertion. The bypass used in this reproduction relies on H2's escaped semicolon syntax inside a connection property value. The payload stores a `db` value like: ```text file:/plugins/sample-database.db;USER=GUEST;PASSWORD=guest;TRACE_LEVEL_SYSTEM_OUT=1\;CALL CSVWRITE('/tmp/pruva_vuln_attempt_1_marker.csv','SELECT 598261')=0 ``` Metabase v1.61.1 persisted this string via the destination-database API. When a later `/api/dataset` request caused Metabase to open that database, H2 interpreted the escaped semicolon and executed the injected `CALL CSVWRITE(...)` statement. The fixed commit `74032e5e0a5a70dc45a6a744d37b9ba24eee8d01` introduces `driver/validate-db-details!` and calls it from the destination database creation path, so H2 details are rejected before persistence. The reproduction script records this fix diff in `bundle/logs/fixed_commit_74032e5_relevant_diff.log`. ## Reproduction Steps 1. Run `bundle/repro/reproduction_steps.sh` from the bundle root, or with `PRUVA_ROOT` pointing at the bundle directory. 2. The script: - reuses/clones the Metabase repository in the prepared project cache and records the fixed commit and parent commit; - starts real Metabase Enterprise Docker images `metabase/metabase-enterprise:v1.61.1` and `metabase/metabase-enterprise:v1.61.2`; - initializes an administrator account through `/api/setup` and authenticates through `/api/session`; - enables database routing through the real Enterprise API; - sends the malicious destination database creation request to `/api/ee/database-routing/destination-database` twice against the vulnerable product; - forces the persisted destination connection through `POST /api/dataset` and verifies marker files created inside the vulnerable Metabase container; - sends the same payload twice to the fixed product and verifies it is rejected. 3. Expected evidence: `bundle/logs/reproduction_steps.log` should show two vulnerable marker creations and two fixed rejections. `bundle/repro/runtime_manifest.json` should have `service_started=true`, `healthcheck_passed=true`, and `target_path_reached=true`. ## Evidence - Primary runtime log: `bundle/logs/reproduction_steps.log`. - Runtime manifest: `bundle/repro/runtime_manifest.json`. - Vulnerable product logs: `bundle/logs/vulnerable_metabase.log`. - Fixed product logs: `bundle/logs/fixed_metabase.log`. - Request/response artifacts: - `bundle/logs/vuln_attempt_1_api.request.json` - `bundle/logs/vuln_attempt_1_api.response.json` - `bundle/logs/vuln_attempt_1_api.query_response.json` - `bundle/logs/vuln_attempt_2_api.request.json` - `bundle/logs/vuln_attempt_2_api.response.json` - `bundle/logs/vuln_attempt_2_api.query_response.json` - `bundle/logs/fixed_attempt_1_api.request.json` - `bundle/logs/fixed_attempt_1_api.response.json` - `bundle/logs/fixed_attempt_2_api.request.json` - `bundle/logs/fixed_attempt_2_api.response.json` - Marker artifacts copied out of the vulnerable container: - `bundle/artifacts/metabase-cve-2026-59826/vuln_attempt_1_marker.txt` contains the value written by H2 SQL execution. - `bundle/artifacts/metabase-cve-2026-59826/vuln_attempt_2_marker.txt` contains the value written by the second H2 SQL execution. - Fixed-version evidence: the final run logged `fixed attempt 1 destination API HTTP status=400` and `fixed attempt 2 destination API HTTP status=400`, both with `H2 is not supported as a data warehouse`, confirming the new validation path blocked H2 details before execution. - Source/patch evidence: - `bundle/logs/fixed_commit_74032e5.patch_summary.log` - `bundle/logs/fixed_commit_74032e5_relevant_diff.log` Key excerpts from the successful final run include: ```text vuln attempt 1 destination API HTTP status=200 vuln attempt 1 dataset API HTTP status=202 destination_id=2 vuln attempt 1 marker created inside Metabase container: "598261" "598261" vuln attempt 2 destination API HTTP status=200 vuln attempt 2 dataset API HTTP status=202 destination_id=3 vuln attempt 2 marker created inside Metabase container: "598262" "598262" fixed attempt 1 destination API HTTP status=400 body={"pruva-cve-59826-fixed-1":{"message":"H2 is not supported as a data warehouse"}} fixed attempt 1 was blocked by fixed validation as expected fixed attempt 2 destination API HTTP status=400 body={"pruva-cve-59826-fixed-2":{"message":"H2 is not supported as a data warehouse"}} fixed attempt 2 was blocked by fixed validation as expected ``` Environment details: Docker was used to run the vendor Metabase Enterprise images. The script starts the product on localhost ports within the worker network and captures product loader/cmdline evidence in `bundle/logs/vulnerable_loader_evidence.log` and `bundle/logs/fixed_loader_evidence.log`. Because database-routing is an Enterprise feature and external license validation is not available in the isolated repro environment, the script starts the product through a minimal Clojure wrapper that only stubs the premium-token check to enable the database-routing feature; the vulnerable API, H2 driver, connection handling, and fixed validation code paths remain the real product code from the vendor images. ## Recommendations / Next Steps - Upgrade Metabase to a fixed version: `1.58.15.1`, `1.59.12`, `1.60.6.3`/`1.60.7`, `1.61.2`, or later. - Keep `driver/validate-db-details!` checks independent of reachability testing. Creation paths that intentionally skip connection tests must still reject unsafe details before persistence. - Add regression tests for all database creation/persistence paths, including destination databases, write/admin connection details, serialization/import paths, and any route that stores H2 details without immediately connecting. - Specifically test H2 escaped-semicolon property values, uppercase/lowercase property names, and non-`INIT` properties whose values contain semicolon-delimited SQL. ## Additional Notes - Idempotency confirmation: `bundle/repro/reproduction_steps.sh` was run successfully twice consecutively after the final fix to the script. Each successful run starts fresh vulnerable and fixed containers, initializes fresh application databases, performs two vulnerable attempts, and performs two fixed negative-control attempts. - The script references the fixed commit `74032e5e0a5a70dc45a6a744d37b9ba24eee8d01` directly and resolves its parent as the vulnerable source identity, satisfying the fixed-commit anchoring rule. - The proof demonstrates the strongest runtime surface available for this claim: real HTTP API requests against running Metabase Enterprise containers. The wrapper used to bypass offline license validation is limited to feature gating and is not a mock of the vulnerable endpoint or H2 execution path. ## Reproduction Details Reproduced: 2026-07-17T14:11:14.209Z Duration: 5577 seconds Tool calls: 348 Turns: Unknown Handoffs: 2 ## Quick Verification Run one of these commands to verify locally: pruva-verify REPRO-2026-00293 pruva-verify CVE-2026-59826 Or open in GitHub Codespaces (zero-friction, auto-runs): https://github.com/codespaces/new?ref=repro/REPRO-2026-00293&repo=N3mes1s/pruva-sandbox Or download and run the script manually: curl -O https://api.pruva.dev/v1/reproductions/REPRO-2026-00293/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-59826 - Source: https://github.com/metabase/metabase ## Artifacts - bundle/repro/reproduction_steps.sh (reproduction_script, 16907 bytes) - bundle/repro/rca_report.md (analysis, 9672 bytes) - bundle/vuln_variant/reproduction_steps.sh (reproduction_script, 18296 bytes) - bundle/vuln_variant/rca_report.md (analysis, 12386 bytes) - bundle/logs/fixed_commit_74032e5_relevant_diff.log (log, 4886 bytes) - bundle/logs/vulnerable_loader_evidence.log (log, 622 bytes) - bundle/logs/fixed_loader_evidence.log (log, 622 bytes) - bundle/vuln_variant/source_identity.json (other, 975 bytes) - bundle/vuln_variant/root_cause_equivalence.json (other, 1055 bytes) - bundle/logs/vuln_variant/fixed_tag_variant_code_anchors.log (log, 21953 bytes) - bundle/repro/validation_verdict.json (other, 901 bytes) - bundle/repro/runtime_manifest.json (other, 1421 bytes) - bundle/logs/reproduction_steps.log (log, 7911 bytes) - bundle/logs/vuln_attempt_1_api.request.json (other, 260 bytes) - bundle/logs/vuln_attempt_1_api.response.json (other, 1969 bytes) - bundle/logs/vuln_attempt_1_api.query_response.json (other, 1587 bytes) - bundle/logs/vuln_attempt_2_api.request.json (other, 260 bytes) - bundle/logs/vuln_attempt_2_api.response.json (other, 1970 bytes) - bundle/logs/vuln_attempt_2_api.query_response.json (other, 1586 bytes) - bundle/logs/fixed_attempt_1_api.request.json (other, 262 bytes) - bundle/logs/fixed_attempt_1_api.response.json (other, 81 bytes) - bundle/logs/fixed_attempt_2_api.request.json (other, 262 bytes) - bundle/logs/fixed_attempt_2_api.response.json (other, 81 bytes) - bundle/artifacts/metabase-cve-2026-59826/vuln_attempt_1_marker.txt (other, 18 bytes) - bundle/artifacts/metabase-cve-2026-59826/vuln_attempt_2_marker.txt (other, 18 bytes) - bundle/logs/vulnerable_metabase.log (log, 398276 bytes) - bundle/logs/fixed_metabase.log (log, 396896 bytes) - bundle/vuln_variant/patch_analysis.md (documentation, 9097 bytes) - bundle/vuln_variant/variant_manifest.json (other, 4012 bytes) - bundle/vuln_variant/validation_verdict.json (other, 2651 bytes) - bundle/vuln_variant/runtime_manifest.json (other, 1348 bytes) - bundle/logs/vuln_variant/reproduction_steps.log (log, 4558 bytes) - bundle/logs/vuln_variant/source_versions.txt (other, 287 bytes) - bundle/logs/vuln_variant/vuln_database_create.response.json (other, 53 bytes) - bundle/logs/vuln_variant/fixed_database_create.response.json (other, 53 bytes) - bundle/logs/vuln_variant/vuln_metabase.log (log, 427011 bytes) - bundle/logs/vuln_variant/fixed_metabase.log (log, 426650 bytes) - bundle/logs/vuln_variant/vuln_database_create.request.json (other, 331 bytes) - bundle/logs/vuln_variant/fixed_database_create.request.json (other, 333 bytes) - bundle/logs/vuln_variant/vuln_serialization_import.response.txt (other, 15393 bytes) - bundle/logs/vuln_variant/fixed_serialization_import.response.txt (other, 15393 bytes) - bundle/logs/vuln_variant/vuln_advanced_config_probe.response.txt (other, 30 bytes) - bundle/logs/vuln_variant/fixed_advanced_config_probe.response.txt (other, 30 bytes) ## API Access - JSON: https://api.pruva.dev/v1/reproductions/REPRO-2026-00293 - Script: https://api.pruva.dev/v1/reproductions/REPRO-2026-00293/artifacts/bundle/repro/reproduction_steps.sh - Web: https://pruva.dev/r/REPRO-2026-00293 ## 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