Aug 10, 2026BTA Team10 min read

Metabase's Password Reset Endpoint Writes Attacker SQL Into Its Own Auth Database: How GHSA-vwf4-m7j8-wcjf Turned Framework and Tally's BI Tool Into a Data Warehouse Leak

We spent Monday morning at BlueTeamAutomation reading Framework's breach notice out loud in the SOC, trying to figure out why it landed so hard for us. It was not the payload, which by public accounts is routine contact information without payment data. It was where the attackers landed. A laptop maker's public breach turned out to be a compromise of the BI tool sitting between the analytics team and the customer database, and the bug that got them in was the sort you would happily explain in a first-year application security class.

The unauthenticated SQL injection tracked as GHSA-vwf4-m7j8-wcjf is what pulled down Framework, Tally, and Metabase Cloud itself between August 3 and August 7. Rated CVSS 10.0 with no CVE assigned, per Metabase's own security update, the flaw lives on an endpoint that sits on every self-hosted Metabase instance by default: POST /api/session/reset_password. What makes the story worth an hour of your week is not the SQL injection primitive but what the injection reaches.

The endpoint that talks to the identity store

Metabase ships with its own application database. Self-hosted installs offer an embedded H2 file by default, though most production deployments point Metabase at PostgreSQL or MySQL. That app database holds everything Metabase needs to run: user accounts, session tokens, permission grants, group memberships, and, most importantly for this bug, the stored connection strings and credentials for every data source you have ever asked Metabase to query. In the same schema sit both the identity records that decide who is an admin and the credentials that let admins pivot into your Snowflake warehouse or your production Postgres cluster.

The password reset endpoint has a legitimate job. Metabase generates a token, stores a reset marker on the user row, and expects the follow-up POST /api/session/reset_password to arrive back carrying the same token and a new password. It has to be unauthenticated because a user who has forgotten their password cannot log in to prove intent, so it is exactly the kind of route where a defender's assumption of "at least they were authenticated first" quietly evaporates.

Why one injection is the whole exploit

The unauthenticated write path is where the injection sits. Sending a crafted payload to POST /api/session/reset_password lets the attacker execute arbitrary SQL against the Metabase application database with no credentials required, according to the Metabase advisory. Because the app database is also where the session table lives, the exploit does not need a second stage. The attacker injects a statement that either flips an existing user's is_superuser bit to true, or writes a fresh row into the session table bound to a chosen user id and a token they picked themselves, and then presents that token to any authenticated Metabase route as if they had logged in properly.

The reason the flaw scores CVSS 10.0 is that the primitive collapses initial access, privilege escalation, and credential access into one HTTP request. Once the attacker holds a valid session cookie for an admin account, Metabase itself does the exfil work for them. The admin panel lists every configured data source, the native SQL editor accepts arbitrary queries against them, and the export buttons ship the results out as CSV. You do not need a reverse shell when the compromised app already ships a bulk-export UI wired to every warehouse in the org, which is why the customer records that spilled out at Framework came out looking tidy: names, emails, IPs, billing and shipping addresses, phone numbers and company names, exported the way a Metabase question exports.

The two log lines that give it away

The public detection signature is the cleanest we have seen this quarter. Every write-up we have read, and Metabase's own guidance, converges on a two-log-line pattern: a POST to /api/session/reset_password returning HTTP 400, immediately followed by a GET to /api/user/current returning HTTP 200 from the same source or the same session. The 400 is the endpoint bailing on the malformed request body after the injection has already executed against the database. The 200 that follows is the attacker calling the "who am I" endpoint with the session they just minted, and receiving back the admin identity Metabase now believes them to hold.

For a defender this is a gift. There is no SQL payload to parse out of an HTTP body and no obscure exploit fingerprint to match; you correlate an ingress error with an immediate authenticated success from the same client and treat the match as compromise. Grep that pattern out of the last thirty days of ingress logs and you will know quickly whether your instance was in scope.

What every SOC missed, and what to hunt now

Everyone we compared notes with had a similar reaction: Metabase was not on their radar. It never made the vulnerability dashboard because there is no CVE for the SIEM to correlate against, and it never made a bespoke detection because BI tools rarely earn one. Access logs, if they were shipped at all, sat in a folder next to the analytics tooling and were reviewed by nobody in particular. That gap is what let the campaign travel from the first hit on Metabase Cloud on August 3 through to public victim notifications on August 7 without anyone catching a live exploit in flight.

The hunt is straightforward once you know to run it. In ingress logs, look for the paired sequence: POST /api/session/reset_password 400 followed within a few seconds by GET /api/user/current 200 from the same source IP or session cookie. Then flag any GET /api/database/* or POST /api/dataset/native that arrives shortly after a session you cannot correlate to a real user login in your identity provider. In Metabase's own admin views, review the user table for accounts whose superuser bit changed without a matching audit entry, and pull the query history for bulk selects or CSV exports you cannot attribute to a real analyst.

Map the activity to ATT&CK as T1190 Exploit Public-Facing Application, T1136.001 Create Account (or T1098 Account Manipulation if the attacker flipped an existing user), T1078 Valid Accounts, T1555 Credentials from Password Stores for the stored connection strings, and T1567 Exfiltration Over Web Service for the CSV downloads.

The remediation you cannot skip

Upgrade first. Patched builds are 0.58.24, 0.59.21, 0.60.17, 0.61.11, 0.62.9, and 0.63.5, along with the enterprise 1.x equivalents on the same branches, per the Metabase advisory. Metabase Cloud is already patched server side. There is no safe workaround for self-hosted instances, because the endpoint is load bearing for the password reset flow and cannot be blocked at the ingress without breaking the feature. If you cannot patch inside your maintenance window, the only defensible move is to firewall the Metabase web tier off the public internet until the upgrade lands.

Then treat every credential Metabase held as burned. Rotate the passwords and service tokens for every database, warehouse, and SaaS API Metabase touches, and revoke all active user sessions to force a re-login. Audit the user table for unexpected admin flags, then pull the query history for bulk SELECT * and native-SQL exports you cannot explain, cross-referenced against the two-log-line IOC window in your ingress logs.

Verify the patch actually held rather than trusting the version banner. Send a benign reset request against the upgraded endpoint and confirm it processes cleanly, then send a payload matching the exploit shape and confirm it is rejected without any side effect landing in the app database. If Metabase points at an external PostgreSQL or MySQL, enable query logging on that app database for a week so any unexpected writes to the user or session tables are caught in the SIEM the moment they happen.

This is the loop we built BlueTeamAutomation around. BASzy replays the exact /api/session/reset_password primitive against your instance, the paired 400 to 200 detection lights up in your SIEM the moment it fires, and the SOAR runbook that rotates every downstream data source credential runs automatically instead of being a paragraph in an incident retro you write three days later.

Prove your Metabase patch actually held.

BASzy replays the reset_password primitive against your upgraded instance, watches for the two-log-line signature, and drives the credential rotation runbook so the closure is evidenced instead of assumed.

Talk to us →