
Detector + root-cause analysis for CVE-2026-72898 (Metabase unauthenticated SQLi via reset_password)
Unauthenticated SQL injection in Metabase via POST /api/session/reset_password.
CVSS 10.0, in the CISA KEV catalog, exploited in the wild.
An undeclared field in the password-reset request body reaches the application-database
user lookup as a HoneySQL structure rather than a validated identifier. A JSON object
key becomes a Clojure keyword, so {"raw": "<SQL>"} compiles to HoneySQL's clause
and is emitted — arbitrary SQL against the Metabase
application database, with no authentication.
:rawThis repository contains a detector (a blind, time-based verdict tool) and a root-cause analysis derived from the vendor patch. It is intended for defenders verifying their own exposure and for security researchers studying the bug. It does not exfiltrate data or provide a weaponised admin-takeover exploit.
cve-2026-72898.py — version-gated, fail-closed detector (VULN / SAFE / UNTESTED)TECHNICAL.md — full root-cause analysis from the v0.63.2 → v0.63.15 patch difflab/docker-compose.yml — a vulnerable + patched instance for reproductionThe detector reports VULN against an affected instance and SAFE against a patched one —
and UNTESTED (never a false SAFE) when a request is throttled, cached, or never reaches
the app. A recording is also provided as an asciinema cast.
| Branch | Vulnerable | Fixed |
|---|---|---|
| 0.58 / 1.58 | < 0.58.24 | 0.58.24 |
| 0.59 / 1.59 | < 0.59.21 | 0.59.21 |
| 0.60 / 1.60 | < 0.60.17 | 0.60.17 |
| 0.61 / 1.61 | < 0.61.11 | 0.61.11 |
| 0.62 / 1.62 | < 0.62.9 | 0.62.9 |
| 0.63 / 1.63 | < 0.63.5 | 0.63.5 |
Advisory: GHSA-vwf4-m7j8-wcjf. If you run an affected version, upgrade now — this is being exploited in the wild.
POST /api/session/reset_password HTTP/1.1
Content-Type: application/json
{"token":"1_00000000-0000-0000-0000-000000000000",
"password":"Placeholder1!",
"user-id":{"raw":"<ARBITRARY SQL>"}}
yields, in the application database:
SELECT id, ... FROM core_user WHERE id = (<ARBITRARY SQL>)
The bug is blind — the reset flow swallows SQL errors and returns an identical
400 {"errors":{"password":"Invalid reset token"}} in every case — so the detector uses a
timing oracle. See TECHNICAL.md for the full data flow and the patch diff.
python3 cve-2026-72898.py https://metabase.example.com # H2 app db (default)
python3 cve-2026-72898.py https://metabase.example.com --engine postgres # pg_sleep oracle
python3 cve-2026-72898.py https://metabase.example.com --json --trials 5
Exit code 0 = VULN, 1 = SAFE or UNTESTED.
Verdicts are VULN / SAFE / UNTESTED — never "probably fine". The tool:
--force overrides deliberately;X-Forwarded-For per request, because the reset endpoint throttles at 10 attempts
and the throttle response is a fast 400 that otherwise mimics a negative;sum(x) over SYSTEM_RANGE) that the engine cannot optimise away, and
requires the delay to clear both an absolute floor and the control's own timing spread;UNTESTED, not SAFE.cd lab
docker compose up -d # 3300 = vulnerable v0.63.2, 3301 = patched v0.63.15
# wait ~90s for first-run DB init
python3 ../cve-2026-72898.py http://127.0.0.1:3300 # -> VULN
python3 ../cve-2026-72898.py http://127.0.0.1:3301 --force # -> SAFE
Verified: v0.63.2 shows an 8+ second delta that scales linearly with the payload row count (5M→0.85s, 10M→1.96s, 20M→4.61s, 40M→7.97s); v0.63.15 shows no differential.
Test only systems you own or are explicitly authorised to assess. Unauthorised access to computer systems is illegal in most jurisdictions. This tool detects the vulnerability with a benign timing probe and intentionally does not read, modify, or exfiltrate data.
MIT — see LICENSE.