CVE-2026-72898 is an unauthenticated SQL injection in Metabase's password reset endpoint that hands an attacker full administrative control of the instance. NVD scores it 10.0 under both CVSS 3.1 (AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H) and CVSS 4.0, and classifies it as CWE-89. CISA added it to the Known Exploited Vulnerabilities catalog on August 11, 2026 with a remediation deadline of August 14, a three day window that is unusually compressed even for KEV.
The sequencing matters more than the score. Metabase disclosed that its own Metabase Cloud environment was attacked with this bug as a zero day before any patch existed, with unauthorized activity beginning August 2 and 3 and customer notifications going out on August 6. Framework, Tally, n8n, Kilo Code and ChecklyHQ have since published first party breach disclosures. Public proof of concept code was open sourced on August 10 per Wiz Research, which widened the attacker pool past whoever ran the original operation. EPSS for this CVE sits at 0.0069, roughly the 49th percentile, and that number is simply lagging: confirmed in the wild exploitation plus public exploit code is the signal to schedule against, not the probability model.
The operational consequence of the zero day window is that patching is necessary and not sufficient. If your instance was internet reachable at any point since August 2, treat the upgrade as step one of an incident response, not as the end of the work.
Am I vulnerable?
Exposure is a function of version. Anything in the 1.58 release train or later, up to the patched release for your line, is affected. Metabase states that versions below 0.58 are not vulnerable, and Wiz Research dates the regression to an authentication module refactor in v1.58. Metabase Cloud instances were patched by the vendor.
Affected versions
| Release line |
Vulnerable range |
Minimum safe version |
| x.58 |
x.58.0 through x.58.23 |
0.58.24 / 1.58.24 |
| x.59 |
x.59.0 through x.59.20 |
0.59.21 / 1.59.21 |
| x.60 |
x.60.0 through x.60.16 |
0.60.17 / 1.60.17 |
| x.61 |
x.61.0 through x.61.10 |
0.61.11 / 1.61.11 |
| x.62 |
x.62.0 through x.62.8 |
0.62.9 / 1.62.9 |
| x.63 |
x.63.0 through x.63.4 |
0.63.5 / 1.63.5 |
| below 0.58 |
not affected |
n/a |
A 0. prefix is the OSS build and 1. is Enterprise/Pro; the numbering is otherwise identical. One caveat on the x.63 row: the GitHub advisory's affected-versions field reads < x.63.3, while its own patched-versions list names x.63.5, and runZero independently states the vulnerable range runs through 0.63.4. Read the table as "below the patched version is vulnerable" rather than trusting the advisory's range field. Version 0.63.4 is vulnerable.
Diagnostic checks
- Any reachable instance (fastest):
curl -4 -s --max-time 10 https://metabase.example.com/api/session/properties | jq -r '.version.tag'. The version setting is public by design, so this needs no credentials. Vulnerable if the tag is below the safe version for its line, for example v0.63.4 or v1.62.8.
- Docker:
docker ps --format '{{.Names}}\t{{.Image}}' | grep -i metabase. Vulnerable if the tag is below the safe release. A tag of :latest tells you nothing, so corroborate with the version probe above.
- Kubernetes:
kubectl get deploy,statefulset -A -o jsonpath='{range .items[*]}{.metadata.namespace}{"/"}{.metadata.name}{"\t"}{range .spec.template.spec.containers[*]}{.image}{" "}{end}{"\n"}{end}' | grep -i metabase.
- On-prem JAR:
unzip -p /path/to/metabase.jar version.properties. Returns a tag= line that is authoritative even when the file is named generically. Locate JARs first with find / -name 'metabase*.jar' -type f 2>/dev/null.
- Compromise hunt: Metabase published an exploitation indicator. In your reverse proxy access logs, look for a POST to
/api/session/reset_password returning HTTP 400 followed seconds later by a GET to /api/user/current returning HTTP 200 from the same source IP. Resecurity adds that the follow-on response carries "is_superuser": true. Absence of that pattern is not proof of safety, since most log retention windows predate August 2.
Metabase is frequently deployed by analytics teams outside the asset inventory, so a version check on the instances you know about will undercount. Sweep for it: runZero suggests the console query vendor:=Metabase AND product:=Metabase, and Shodan tracks roughly 2,500 internet exposed instances.
Vulnerability
NVD classifies this as CWE-89, improper neutralization of special elements in an SQL command. The reachable surface is POST /api/session/reset_password, which is unauthenticated by design because a user completing a password reset has no session yet.
Wiz Research describes the root cause as a chain of three behaviors rather than a single missing escape. Metabase's JSON handling turns arbitrary client supplied keys into Clojure keywords, so a request body can carry fields the endpoint never declared. A map merge in the authentication path did not strip those undeclared keys when authentication failed. And HoneySQL, the query builder Metabase compiles with, treats a value of the form {:raw "..."} as literal SQL to splice into the statement, deliberately bypassing parameter binding. An attacker supplied user identifier therefore reached the query builder as a structured object rather than a validated integer, and was compiled as raw SQL against the Metabase application database. The patch validates that the identifier is a positive integer before any database query runs, and constrains the endpoint schema to token and password so undeclared fields are rejected outright.
Bishop Fox frames the same defect as a failure to restrict undeclared fields in the request body, and notes an impact nuance the vector implies but most writeups skip: depending on the application database engine and its configuration, exploitation can affect availability as well as confidentiality and integrity. Bishop Fox also recommends a post patch validation step distinct from a version check, which is to confirm behaviorally that undeclared fields sent to the endpoint no longer influence application database queries.
The application database is the target that makes this a 10.0 rather than a serious but contained injection. It stores Metabase's user table, session table, API keys, and the saved connection credentials for every data source the instance reports on. Injection against it converts directly into administrator control.
Threat model
Who would exploit this: The most likely operators fall into three categories: opportunistic mass exploitation crews who scan for Metabase on ports 3000 and 443, fingerprint the vulnerable release trains and hit every reachable instance; data theft and extortion groups who treat a BI platform as a shortcut to an organization's warehouse rather than as a target in itself; and initial access brokers who convert unauthenticated admin takeover into resale grade access. The bar is low (network reachability, no credentials, no user interaction), so the population is broad rather than elite, and the public proof of concept released on August 10 widened it further.
What they're after:
- Data exfiltration, the primary observed goal: querying connected warehouses and application tables for customer PII, credentials and business data
- Foothold: unauthenticated admin takeover of an internet facing instance in a single request chain
- Lateral movement: pivoting from Metabase into the production databases whose connections it stores
- Persistence: attacker created administrator accounts, API keys, altered authentication settings, cleared session tables
- Financial fraud: monetization through extortion, leak site listing, or resale of access
- Destruction: least likely, and nothing observed, since destructive action forfeits the access value that makes this bug attractive
Attack chain: The attacker locates an internet reachable Metabase instance and sends an unauthenticated request to the password reset endpoint, which accepts undeclared fields that reach the application database as SQL rather than as validated input. Injected SQL against that database yields administrator control without any credential or user interaction. From the admin context the attacker reads stored configuration, including saved connections to production databases and warehouses, and issues queries through those saved connections. Persistence follows the same path: new admin users, API keys and configuration changes made through the interface the attacker now controls.
Blast radius: A compromised Metabase instance is a credential vault plus a query console for everything it reports on. The application database holds connection details for Postgres, MySQL, SQL Server, Snowflake, BigQuery, Redshift and any other configured source, and Metabase's own advisory states an attacker can steal those credentials and read or export data through those connections. Admin control also exposes instance secrets available to the running application, including embedding and SSO or JWT signing configuration, so authentication can be rewritten and access re-established after a password reset. Where Metabase is self hosted on a cloud VM or in Kubernetes, warehouse connections often ride on the workload's cloud identity, extending reach to whatever that IAM role can read. The confirmed victim disclosures bear this out: n8n reported 136 customer records reached, five of them containing bcrypt hashed passwords; Framework reported customer names, addresses, phone numbers, emails and login IP addresses; Kilo Code reported an exposed Slack access token.
No named operator has been confirmed by CISA, Metabase or a named incident response firm. A leak site claim exists in press reporting, but it is a self claim with no corroboration, so this write-up assigns no attribution.
Mitigation
Patch
Upgrade to the minimum safe release for your line. Metabase Cloud customers are already patched by the vendor.
| Release line |
Upgrade to |
| x.58 |
0.58.24 / 1.58.24 |
| x.59 |
0.59.21 / 1.59.21 |
| x.60 |
0.60.17 / 1.60.17 |
| x.61 |
0.61.11 / 1.61.11 |
| x.62 |
0.62.9 / 1.62.9 |
| x.63 |
0.63.5 / 1.63.5 |
Post upgrade cleanup is mandatory if the endpoint was ever publicly reachable. Because this ran as a zero day from August 2, the vendor directs self hosted operators to: delete all rows in the core_session table to revoke active sessions; review and delete unrecognized API keys; audit administrator accounts for unexpected creation or modification; rotate credentials for every connected database and warehouse; check warehouse access logs for unauthorized queries and exports; and review Metabase activity and query history for anomalies. Credential theft is the primary observed objective, so the credential rotation step is the one that actually closes the incident.
Configuration mitigation
Metabase's stated temporary workaround is to block the vulnerable endpoint until the upgrade lands. This disables completion of self service password resets, so admins must reset user passwords manually while it is in place.
Block the endpoint at nginx:
# Temporary mitigation for CVE-2026-72898 until Metabase is upgraded.
# Side effect: self-service password reset completion stops working.
location = /api/session/reset_password {
access_log /var/log/nginx/metabase_cve_2026_72898.log combined;
return 403;
}
location / {
proxy_pass http://metabase_upstream;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
Allowlist variant, if legitimate resets must keep working (suggested by Resecurity):
location = /api/session/reset_password {
allow 10.0.0.0/8; # corporate VPN / office egress
allow 192.0.2.0/24; # replace with your real trusted ranges
deny all;
access_log /var/log/nginx/metabase_cve_2026_72898.log combined;
proxy_pass http://metabase_upstream;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
Apache httpd equivalent:
<Location "/api/session/reset_password">
Require all denied
</Location>
ProxyPass / http://127.0.0.1:3000/
ProxyPassReverse / http://127.0.0.1:3000/
Code mitigation
Pin the container image to a patched tag rather than tracking :latest, so the running build is auditable against the table above.
# docker-compose.yml
services:
metabase:
image: metabase/metabase:v0.63.5 # or metabase/metabase-enterprise:v1.63.5
# Kubernetes deployment
spec:
template:
spec:
containers:
- name: metabase
image: metabase/metabase:v0.63.5
imagePullPolicy: IfNotPresent
Compensating controls
The endpoint is unauthenticated by design, so the durable control is network exposure reduction: put Metabase behind a VPN or an SSO proxy rather than on the open internet. Wiz found that about 13 percent of cloud environments run self hosted Metabase and roughly a quarter of those are fully internet accessible, which is the population this bug was written against. Beyond exposure, scope the blast radius before the next one: give each Metabase data source connection a dedicated read only warehouse account rather than a shared or privileged one, restrict those accounts to the schemas the dashboards actually need, and alert on their query volume. Segment the Metabase host so it cannot reach infrastructure outside its declared data sources, and if it runs on a cloud workload identity, scope that role to the same narrow set.
Detection (starter rules, validate before deploying)
These rules are AI-generated starter content. Test against your own telemetry and tune falsepositives before deploying to production.
Network and log signals
- Volume anomaly on the reset flow: a spike in requests to
/api/session/reset_password, /api/session/password_reset_token_valid or /auth/reset_password out of proportion to your normal reset rate, especially from a small set of source IPs or from cloud and VPS ranges that never host your users.
- Scan rate access from a single IP: dozens to thousands of reset endpoint hits per minute, sequential or randomized token values, generic or absent User-Agent, no preceding
forgot_password request, and none of the static asset fetches a real browser session would generate.
- Response time anomalies suggesting time based blind injection: reset endpoint responses clustering at round latencies (about 2s, 5s, 10s) when the endpoint normally answers in tens of milliseconds. Chart p95 latency for the reset paths alone; a bimodal distribution is the tell.
- The vendor's exploitation indicator: POST to
/api/session/reset_password returning 400 followed within seconds by GET to /api/user/current returning 200 from the same source, with "is_superuser": true in the response body.
- Metabase application log anomalies: JDBC or SQL exception stack traces (
org.h2, org.postgresql.util.PSQLException, com.mysql.cj, next.jdbc) raised from the metabase.api.session namespace. A SQL parser error surfacing from the session API is not a normal user error path.
- New privileged accounts in the application database: rows in
core_user with is_superuser and is_active true created inside the suspicious window, an existing account silently promoted, or new API keys. Also check for password_reset_token values set on accounts whose owners never requested a reset.
- New or modified database connection entries: additions or edits under Admin > Databases (
metabase_database rows), changed connection strings, or a connection repointed at attacker infrastructure.
- Unexpected egress from the Metabase host or container: new long lived TCP sessions, DNS lookups for freshly registered domains, or any child process of the JVM (
sh, curl, wget, python). Metabase is a predictable egress talker, so anything new deserves review.
- Warehouse side telemetry: queries under the Metabase service account outside business hours, or query shapes Metabase would never emit, such as
information_schema enumeration, whole table selects, or COPY and UNLOAD to external storage.
- Configuration drift: changes to SMTP host, site URL, JWT or SSO shared secrets, the embedding secret key, or LDAP bind credentials, plus new scheduled email and Slack subscriptions that would exfiltrate dashboard content on a timer.
YARA
rule Metabase_CVE_2026_72898_ResetPassword_SQLi_LogArtifact
{
meta:
description = "AI-generated STARTER rule. Flags web-server, proxy or WAF access-log lines and text-extracted HTTP request captures in which a Metabase reset-password endpoint is requested together with SQL metacharacters or injection markers, the behaviour underlying CVE-2026-72898. Validate in your environment before deploying."
author = "CVE Brief - AI-generated starter rule"
date = "2026-08-12"
cve = "CVE-2026-72898"
reference = "https://nvd.nist.gov/vuln/detail/CVE-2026-72898"
scope = "Offline triage of exported access logs (nginx, Apache, HAProxy, ALB, Cloudflare), WAF request captures, container stdout logs and text-extracted PCAP payloads. This is NOT a binary scanner: the vulnerability drops no file."
confidence = "medium"
strings:
// Metabase reset-password surfaces
$p1 = "/api/session/reset_password" ascii nocase
$p2 = "/api/session/password_reset_token_valid" ascii nocase
$p3 = "/auth/reset_password" ascii nocase
// Generic SQL injection markers, raw and URL-encoded
$sql1 = "union select" ascii nocase
$sql2 = "union%20select" ascii nocase
$sql3 = "union+select" ascii nocase
$sql4 = "information_schema" ascii nocase
$sql5 = "pg_sleep" ascii nocase
$sql6 = "waitfor delay" ascii nocase
$sql7 = "waitfor%20delay" ascii nocase
$sql8 = "benchmark(" ascii nocase
$sql9 = "dbms_pipe.receive_message" ascii nocase
$sql10 = "extractvalue(" ascii nocase
$sql11 = "updatexml(" ascii nocase
$sql12 = "case%20when" ascii nocase
$sql13 = "%27%20or%20" ascii nocase
$sql14 = "%27%20and%20" ascii nocase
$sql15 = "%27%3b" ascii nocase
// Metabase application-database objects an attacker would target
$tgt1 = "core_user" ascii nocase
$tgt2 = "is_superuser" ascii nocase
$tgt3 = "core_session" ascii nocase
$tgt4 = "metabase_database" ascii nocase
// Reset token parameter carrying quote or comment metacharacters
$re_token = /token=[A-Za-z0-9_.\-]{0,64}(%27|'|%22|--|%2d%2d|\/\*)/ nocase
condition:
filesize < 512MB
and any of ($p*)
and (any of ($sql*) or any of ($tgt*) or $re_token)
}
Sigma
title: Possible Metabase Unauthenticated Reset-Password SQL Injection (CVE-2026-72898)
id: e7b3a91c-5d24-4f68-b0a7-9c31e6d47f52
status: experimental
description: |
AI-generated starter rule. Detects HTTP requests to Metabase reset-password
endpoints (/api/session/reset_password, /api/session/password_reset_token_valid
and the /auth/reset_password front-end route) that carry SQL metacharacters or
injection markers in the reset token or query string. This is the behaviour
underlying CVE-2026-72898, an unauthenticated CVSS 10.0 SQL injection (CWE-89)
in the reset-password token handling path that leads to full admin control,
added to the CISA KEV catalog on 2026-08-11 with confirmed active
exploitation. A legitimate Metabase reset token is an opaque alphanumeric
string, so SQL syntax in that parameter has no benign explanation. Validate in
your environment before deploying.
references:
- https://nvd.nist.gov/vuln/detail/CVE-2026-72898
- https://www.cisa.gov/known-exploited-vulnerabilities-catalog?field_cve=CVE-2026-72898
- https://www.metabase.com/blog/security-update
author: CVE Brief - AI-generated starter rule
date: 2026-08-12
tags:
- attack.initial-access
- attack.t1190
- cve.2026.72898
logsource:
category: webserver
definition: |
Access logs from the reverse proxy, load balancer, WAF or CDN in front of
Metabase (nginx, Apache, HAProxy, Envoy, ALB, Cloudflare), or from Metabase
itself if it is directly exposed. The full request URI including query string
MUST be logged: many default access-log formats drop the query string, which
silently disables the query-string branch of this rule. If your proxy also
logs request bodies, map the body field onto the selection_sqli_body branch,
since the POST reset_password call carries the token in the JSON body.
detection:
selection_endpoint:
cs-uri-stem|contains:
- '/api/session/reset_password'
- '/api/session/password_reset_token_valid'
- '/auth/reset_password'
- '/api/session/forgot_password'
selection_sqli_query:
cs-uri-query|contains:
- "union select"
- "union%20select"
- "union+select"
- "information_schema"
- "pg_sleep"
- "sleep("
- "benchmark("
- "waitfor delay"
- "waitfor%20delay"
- "dbms_pipe.receive_message"
- "extractvalue"
- "updatexml"
- "case when"
- "'--"
- "%27--"
- "%27%20"
- "%27)"
- "/*"
- "%2f%2a"
- "%3b"
- "%00"
selection_sqli_body:
c-body|contains:
- "union select"
- "information_schema"
- "pg_sleep"
- "waitfor delay"
- "benchmark("
- "extractvalue"
- "updatexml"
- "dbms_pipe.receive_message"
condition: selection_endpoint and 1 of selection_sqli_*
fields:
- src_ip
- cs-method
- c-uri
- cs-uri-query
- sc-status
- time_taken
- c-useragent
falsepositives:
- Legitimate password reset traffic where the token or surrounding query string
contains URL-encoded characters that overlap these patterns. Tokens are
opaque, so review the decoded value before escalating.
- Authorised vulnerability scanners, DAST tooling and penetration tests, which
will fire this rule heavily from a small number of source IPs. Maintain an
allowlist and correlate with the testing schedule.
- Internet background noise from opportunistic mass scanners hitting every
known endpoint with generic SQLi probes. Real attempts, but not evidence of
compromise on their own. Pivot on HTTP 200 responses, long response times
and any follow-on admin activity.
- Security products that replay or sanitise requests, and synthetic monitoring
that exercises the reset flow with crafted inputs.
- Log pipelines that concatenate several requests onto one line, which can put
an unrelated SQL string next to a reset_password path.
level: critical
Rule notes
Both rules key on the only reliably observable primitive for this bug, a request to a Metabase reset password path whose token or query string carries SQL metacharacters, rather than on file hashes, since nothing is dropped to disk. Known limits: the POST body carries the token, so deployments that log only the request URI will see nothing unless they map the body field or enable WAF body capture; a purely encoded or blind time based payload with no recognisable keywords can evade both rules, so pair them with the latency, new admin and egress signals above; and the YARA rule matches file wide rather than line wise, so a large mixed log can co-locate an unrelated SQL string with an unrelated reset request and produce a false hit.
References