cd /news/ai-safety/metabase-cloud-the-winner-takes-it-a… · home topics ai-safety article
[ARTICLE · art-64142] src=hacktron.ai ↗ pub= topic=ai-safety verified=true sentiment=↓ negative

Metabase Cloud: The winner takes it all

Hacktron researchers discovered critical vulnerabilities in Metabase Cloud that could have allowed attackers to compromise all cloud tenants. By exploiting flaws in H2 database connection handling and Kubernetes node authorizer misconfigurations, they achieved remote code execution and accessed secrets of 24 tenant pods, including database credentials and AWS keys.

read5 min views15 publishedJun 25, 2026
Metabase Cloud: The winner takes it all
Image: Hacktron (auto-discovered)

With how many bugs AI is starting to find, the hard part is no longer finding them. It is knowing which ones actually matter:

  • Which are interesting?
  • Which are devastating?
  • Which ones let an attacker compromise the entire infra?

At Hacktron be it research or an AI pentest our goal is to find the bugs that can completely compromise companies, and helping secure them before attackers do.

Previously we wrote SupaPwn showing how we got Supabase tenants access. This time it is Metabase, where we could have pwned every cloud tenant.

H2 INIT-imidates again #

H2 is an embedded Java database that Metabase ships by default, and it is not a plain data store. Its SQL can read and write files on the host and call Java methods inside the Metabase process, so making H2 run our SQL is effectively an RCE primitive on the server.

This is a known attack, and hence Metabase has preventions for this, one strips INIT=

out of the connection string, and one rejects any H2 connection that carries an INIT

.

However, both checks can be bypassed! They only look for INIT

inside the URL. So instead of putting it in the URL, we put it in its own separate field next to the URL.

The reason it survives is that only the URL ever gets cleaned. The H2 driver runs the sanitizer on the db

URL and nothing else:

Every other field is passed on untouched to the connection builder, which copies it into the connection as-is, including INIT

:

That last line, dissoc opts :db

, takes everything except the URL and forwards it as-is, so our INIT

field flows along.

But we still need to get our malicious H2 database saved, and H2 connections are turned off by default. The normal POST /api/database

path runs can-connect? :h2 , which throws “H2 is not supported as a data warehouse” before our INIT

is ever reached, so nothing gets stored. The way in is the EE serialization import endpoint: it builds Database records straight from the uploaded YAML and never calls can-connect?

, so our H2 record (with its INIT

field) gets persisted unchecked. Once it exists, we ask Metabase to sync the database. Sync flips that switch on by itself before opening the connection:

With being able to run H2, we use an arb file read/write to get RCE. We use two INIT statements.

The first INIT

abuses H2’s CSVWRITE

as a plain file-write, dropping our Clojure code at /tmp/evil.clj

.

The second INIT

points a SQL alias at a Java method already loaded in the Metabase process, clojure.lang.Compiler.loadFile

, then calls it on the file we just wrote:

Running against our Metabase Cloud tenant, gives us code execution inside our pod in Metabase Cloud infra.

Pwning other tenants #

The cloud environment had a chain of misconfigurations that turned our pod into disclosing secrets of every other tenant’s (pods).

The EC2 Instance Metadata Service was accessible from inside the pod, which gives us temporary AWS credentials for the node’s IAM role, eks-cluster-prod-metabase-1-nodes

.

With those credentials, EC2, ECR, S3, and EKS describe calls all succeeded, exposing the full production instance inventory and container registry.

We used the obtained node credentials to mint an EKS bearer token and authenticate to the cluster API as - system:node:ip-10-241-27-69.ec2.internal

Kubernetes’ node authorizer intentionally lets a node read the specs and secrets of pods scheduled on it. The security model assumes that compromising a node already means compromising its pods. Hence the node identity we just got allows us to read all other pods (tenants) and their secrets.

The node was running 24 tenant Metabase pods, each in its own namespace. Every pod spec carries its environment variables, and the node authorizer let us read all of them.

These were the tenant pods we got access to:

Credential What it grants
PostgreSQL connection strings Full read/write to each tenant’s database
MB_ENCRYPTION_SECRET_KEY Decrypt all stored Metabase secrets per tenant
MB_API_KEY
Site URLs Identify each customer

A small sample of the confirmed tenant instances (site URLs and API keys redacted):

Namespace Site URL API Key (truncated)
hosting-94950cc15a15c03e https://.metabaseapp.com- mb_api_key_ec191ad1…
hosting-d89f6af7e1ef27fc https://data.******.com mb_api_key_9481e01e…
hosting-68e56f67966baef2 https://**************.metabaseapp.com mb_api_key_96277d1b…

Fun fact: While doing this recon on a Friday night, this triggered some logging alerts for Metabase team xD

With the site URLs + MB_API_KEY in hand we can access the Metabase cloud instance as admin user.

We’ve made a mistake in our initial assumption due to lack of testing of the obtained env vars of the pod. The MB_API_KEY doesn’t grant access to the instance. However other env vars including database credentials did persist though were not tested on the env as it would be unauthorized, per Metabase team those would not be useful in gaining further access to the system.

This can be scaled further: with roughly 24 tenants per node and many nodes in the prod-metabase-1

cluster, each node compromise yields all of its tenants.

Conclusion #

This chained exploitation highlights that a single sanitization oversight can cascade into a total multi-tenant cluster compromise. Security for such systems cannot rely on single-point defenses, especially when complex third-party drivers or legacy protocols are involved.

The Metabase team was able to patch the underlying connection logic and harden their cloud infrastructure boundaries before malicious actors could exploit the blast radius.

As AI-driven testing continues to uncover hidden entry points, building resilient, defense-in-depth architectures is the only way to ensure that a compromise at the application layer doesn’t equal a compromise of the entire cloud.

── more in #ai-safety 4 stories · sorted by recency
── more on @hacktron 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/metabase-cloud-the-w…] indexed:0 read:5min 2026-06-25 ·