I recently found a critical authentication bypass on a private bug bounty program that let me dump 21MB of production data — user records, internal financial data, and admin credentials — starting from zero authentication. Four HTTP requests, no account needed.
Here's how it went down.
— -
## The Recon
I was poking around the target's platform, looking at how the frontend loaded data. The usual stuff — intercepting requests in Burp, reading JavaScript bundles, mapping out API endpoints.
One thing caught my eye. The platform had a public endpoint that generated anonymous JWTs. These tokens were meant for unauthenticated visitors browsing the site — basic stuff like loading content before you log in. The token had a claim marking it as an anonymous session.
On its own, that's not interesting. Lots of apps issue anonymous tokens for public content. The question is always the same: **what else accepts this token?**
## Following the Token
I started replaying the anonymous JWT against every internal API endpoint I could find. Most of them rejected it properly — 401, 403, move along.
But one endpoint didn't.
There was an internal microservice responsible for real-time data synchronization. Its job was to issue Firebase tokens to authenticated users so the live features could work in the browser. When a logged-in user hit this endpoint, it would validate their session, check their permissions, and hand back a Firebase custom token scoped to their account.
When I sent my anonymous JWT to this same endpoint, it just… accepted it. No validation on the anonymous claim. No check that the token came from an actual authenticated session. It treated my anonymous token the same as a logged-in user's session and handed back a fully privileged Firebase custom token.
But it got better. The response didn't just contain the Firebase token. It also returned:
- Three Firebase API keys (web, iOS, Android) - The Firebase project ID - The database URL - Service account identifiers
Everything I needed to authenticate with Firebase, handed to me in a single response from an endpoint that should have rejected my request.
## Into Firebase
From here the path was straightforward. I took the Firebase custom token and the API key from the response, sent them to Google's Identity Toolkit to exchange for a Firebase ID token, and I was in.
The ID token gave me read access to the production Firebase Realtime Database. Not a sandbox. Not a staging environment. Production.
I also got a refresh token back, which is standard Firebase behavior — but the implication was significant. That refresh token works indefinitely through Google's token endpoint. Meaning an attacker would only need to run this chain once, save the refresh token, and have permanent database access without ever touching the target's infrastructure again.
## What Was in the Database
The database contained a complete internal dataset for one of the platform's core systems. I'm keeping specifics vague to protect the program, but here's the scope of what was accessible:
**User records** — over a thousand individual transaction records, each containing: - A persistent user UUID (uniquely identifies an account) - A partially masked username - The exact transaction amount, down to the cent - The user's region - A millisecond-precision timestamp
I found individual records reaching multi-million dollar amounts.
**Internal financial data** — configurations contained internal fields related to house reserve allocations, showing how much money was being held back versus what was displayed to end users. These ranged from five to six figures each. This is internal data that end users are never supposed to see.
**CRM trigger thresholds** — records showing the exact dollar amounts at which internal marketing events fire. These revealed the operational playbook for when the platform decides to push notifications and campaigns.
**Tens of thousands of unique user UUIDs** — mapped to platform features across multiple regions, separate from the transaction records.
**Full system configurations** — contribution rates, fund types, payout structures, frequency data, and regional settings for every configuration on the platform.
**Historical aggregates** — total aggregate payouts reaching significant nine-figure sums and a large number of high-value winners tracked internally.
On top of all of this, the Firebase token also gave me write access to Firebase Auth. I was able to modify the email address on a high-privileged administrative account, which leaked the original admin email in the response. I confirmed the write worked, then immediately reverted it.
## Real-Time Surveillance
Here's where it gets worse. Firebase Realtime Database supports Server-Sent Events. By adding `Accept: text/event-stream` to the request, I could open a persistent connection that streamed every new database update in real-time.
I set this up against the transaction records path and watched. Within seconds, new records started coming through live — user UUIDs, usernames, amounts, regions, all streaming in as they happened.
Combined with the permanent refresh token, an attacker could build automated surveillance infrastructure that monitors every new transaction indefinitely, refreshing the access token every 55 minutes through Google's servers, never touching the target's systems again. The target would have zero visibility into this.
## The Root Cause
The entire chain traced back to one missing check. The internal endpoint that issued Firebase tokens never validated the anonymous claim on the incoming JWT. It was designed to serve authenticated users but treated anonymous tokens identically.
Everything downstream — the database access, the credential leakage, the auth writes, the permanent refresh token — was a consequence of that single broken authentication check.
## The Fix
Simple in concept:
1. Reject anonymous JWTs at the Firebase token endpoint — validate the anonymous claim and deny token generation for unauthenticated sessions 2. Tighten Firebase RTDB security rules so even if a token leaks, it can only read the specific subpaths needed by the client, not the entire dataset 3. Revoke all refresh tokens that were issued to anonymous sessions
## Lessons
**Follow the token.** Anonymous JWTs are common and usually harmless on their own. The vulnerability isn't the anonymous token — it's what accepts it downstream. Every token you find during recon should be replayed against every internal endpoint you can discover.
**Check what comes back in the response body.** The endpoint didn't just return a token — it returned API keys, project IDs, database URLs, and service account identifiers. Sometimes the response to a single request gives you everything you need for the next three steps of the chain.
**Firebase refresh tokens are forever.** If you find any path to a Firebase ID token through a broken auth chain, always check for the refresh token. It turns a one-time access into permanent persistence, and the target has no visibility into refreshes because they happen on Google's infrastructure.
**SSE on Firebase RTDB is underrated.** A lot of researchers stop at "I can read the database" and grab a JSON dump. But if the database has real-time data, opening an SSE stream shows the live impact — you're not just reading historical records, you're watching new ones appear in real-time. This can significantly strengthen your report.
**Test Firebase Auth separately from RTDB.** After getting a Firebase ID token, most people go straight to the database. Don't forget to test `accounts:update` and `accounts:lookup` against the Identity Toolkit. In this case, the same token that gave me database read also gave me Firebase Auth write — two separate impacts from one token.
— -
This was triaged and rewarded as a critical finding. The authentication check was patched and the affected tokens were revoked.
If you're hunting on targets that use Firebase, spend time mapping how tokens flow between the target's API and Firebase's infrastructure. The auth boundaries between "the app" and "Firebase" are where things tend to break.
Happy hunting.