// HackTheBox

Analytics

Metabase pre-auth RCE (CVE-2023-38646) via H2 JDBC injection → env-leaked SSH creds → GameOver(lay) kernel privesc

HackTheBoxEasyLinux Jul 20, 2026 4 min read

Recon

Two ports, and the whole box hinges on what the web server is really hosting:

Port Service Version
22 SSH OpenSSH 8.9p1 Ubuntu 3ubuntu0.4
80 HTTP nginx 1.18.0; redirects → analytical.htb (static landing)

Port 80 doesn’t serve content for the raw IP — it redirects to the vhost analytical.htb, so that name goes into /etc/hosts. analytical.htb itself is just a static marketing landing page, but crawling its links turns up a second vhost, data.analytical.htb, which is added to /etc/hosts alongside the first. That subdomain answers with Metabase, an open-source business-intelligence dashboard. A self-hosted analytics app on its own subdomain is exactly the kind of fat, versioned target worth fingerprinting first — so that’s the thread to pull.

Foothold — CVE-2023-38646

1. Fingerprint Metabase and leak the setup-token

Metabase exposes an unauthenticated settings endpoint, /api/session/properties, that dumps its public configuration as JSON — including the running version and, on a box that was never fully initialised, the one-time setup-token used to gate the first-run wizard:

$ curl -s http://data.analytical.htb/api/session/properties

Two facts fall out of that response:

  • Version v0.46.6 (released 2023-06-29).
  • setup-token 249fa03d-fd94-4d5b-b94f-b4ebf3df681f — the token is non-null, which means the first-run setup flow was never completed and is still open.

Both matter for CVE-2023-38646, a pre-authentication RCE in Metabase. It was fixed in 0.46.6.1; 0.46.6 is squarely in the vulnerable range, and a live setup-token is the exact precondition the exploit needs.

2. Pre-auth RCE via H2 JDBC injection

Metabase ships with an embedded H2 database (a pure-Java SQL engine) for its own sample data. The setup wizard lets you point Metabase at a database by supplying a JDBC connection string, and POST /api/setup/validate “tests” that connection before any account exists — using the leaked setup-token as the only credential. H2’s JDBC driver allows a connection string to define a SQL trigger written in JavaScript, and that trigger code runs on the server when the connection is opened. So a crafted connection string turns “validate my database settings” into “execute my code”: CREATE TRIGGER ... AS '//javascript ...' → command execution as the Metabase process.

The public exploit metabase_rce.py automates this against POST /api/setup/validate. Two implementation details are easy to trip over:

  • The full details object is required. The request’s outer details field must carry every sibling key the endpoint expects — is_on_demand, is_full_sync, is_sample, cache_ttl, refingerprint, auto_run_queries, schedules. Omit any and the server rejects the whole request with 400 {"message":"Vector arg to map conj must be a pair"} before it ever reaches the vulnerable code.
  • The payload is Base64, space-padded. The reverse-shell command is Base64-encoded and padded with spaces so it never ends in an = character — an = would terminate the H2 connection string early and break the injection.

The trigger is wired to fire when Metabase runs its own SELECT against INFORMATION_SCHEMA.TABLES during validation. The HTTP request comes back as a 400 SQL-syntax error, but that’s cosmetic — the trigger’s JavaScript has already executed by then. The result is a reverse shell as uid=2000(metabase) inside the container 71c5ea8a011e.

Lateral — env-leaked SSH credentials

The shell lands in a container, not on the host, so the next question is what the container knows that the host reuses. Metabase is configured through environment variables, and its own process environment hands over a set of credentials:

META_USER=metalytics
META_PASS=An4lytics_ds20223#

The variable names look Metabase-specific, but the values are reused as host SSH credentials — the classic password-reuse pivot from a container out to the box it runs on. They work directly over SSH, dropping a real session as uid=1000(metalytics) and giving up user.txt:

$ ssh metalytics@10.129.229.224

Privilege Escalation — GameOver(lay)

On the host, uname -a reports Ubuntu 22.04.3 on kernel 6.2.0-25-generic. That kernel is vulnerable to GameOver(lay) (CVE-2023-2640 / CVE-2023-3262), a flaw in Ubuntu’s OverlayFS where file capabilities set on a file in an unprivileged user namespace are copied up to the real filesystem — letting an unprivileged user stamp privileged capabilities onto an executable.

The standard unshare -rm one-liner exploits it: create a new user + mount namespace, build an OverlayFS mount, and set the cap_setuid capability on a copy of the ruby binary. Because the capability survives the copy-up onto the host filesystem, that ruby can then call Process::Sys.setuid(0) to become root:

$ ruby -e 'Process::Sys.setuid(0); exec "/bin/bash"'

That returns a root shell — read /root/root.txt to finish the box.

← All writeups