Coverage for Firebase Auth, databases, storage, configuration, functions, hosting, IAM, and messaging.
A practical checklist for testing Firebase services. Each section ends with the relevant OpenFirebase command, or a note when the area is out of scope for the tool and must be tested manually.
Finding criteria are called out per section under Report when.
Scope note This checklist is written for pentests where the goal is full coverage of the Firebase attack surface. Several items here (e.g. email enumeration via accounts:createAuthUri, anonymous auth being enabled with no downstream impact, are typically out of scope or non-reportable on public bug bounty programs. Treat the "Report when" criteria as the bar for a pentest report; on a bounty program, additionally check the program's scope and rules-of-engagement before submitting.
This checklist is tool-agnostic, but some open-source projects automate most of the checks below:
OpenFirebase — Firebase recon and security scanner. Extracts firebase items from APKs/IPAs, scans RTDB / Firestore / Storage / Remote Config / Cloud Functions (unauth + authenticated), supports write checks and scans for hardcoded service accounts/private keys. Most sections below include the matching openfirebase command.
FireSA — Service-account post-exploitation. Use after recovering a service account (such as Firebase Admin SDK) private key (see §10) to enumerate IAM permissions and pivot.
firebase-wordlists — Wordlists for fuzzing Firebase Cloud Functions (httpsCallable) and Firestore, mined from public GitHub source code and ranked by real-world usage across thousands of repos. Drop-in for OpenFirebase (--fuzz-functions), ffuf, and other black-box recon tooling. Included in OpenFirebase.
String resources under res/values/ (typically merged values.xml) after apktool decompile. The original google-services.json is not shipped — Gradle bakes its values into resources.arsc at build time.
What it is: Firebase Authentication is Google's drop-in user sign-in system. It handles email/password, anonymous, phone, and federated providers (Google, Apple, Facebook, SAML, OIDC, ...) and returns a short-lived JWT ID token that every other Firebase service uses to identify the caller.
Firebase Auth is exposed via the Identity Toolkit API at identitytoolkit.googleapis.com. The API key in the client is not a secret on its own — Google explicitly documents this (API keys for Firebase). It only becomes a finding when combined with insecure rules or enabled providers that should not be open.
Anonymous sign-in enabled when it shouldn't be?
Check if email/password signup is allowed to request a bearer token for the next steps
POST /v1/accounts:signUp?key=API_KEY{"email":"test@example.com","password":"Test1234!","returnSecureToken":true}
Email enumeration via accounts:createAuthUri (returns registered: true/false)
POST /v1/accounts:createAuthUri?key=API_KEY{"identifier":"victim@example.com","continueUri":"http://localhost"}
This behavior is configurable; with email enumeration protection enabled the response is uniform (no registered / signinMethods fields). With protection disabled, the response leaks "registered": true|false and "signinMethods": [...] — this is a finding because it lets an attacker confirm whether arbitrary email addresses are users of the app. See Email enumeration protection.
Self-service account deletion enabled? (Identity Platform: User account management → User actions → Enable delete)
POST /v1/accounts:delete?key=API_KEY{"idToken":"<ID_TOKEN_FROM_SIGNIN>"}
A 200 {} response means the signed-in user's account was deleted. When the toggle is off, the API returns ADMIN_ONLY_OPERATION. On legacy Firebase Auth projects (no Identity Platform upgrade) the toggle does not exist and self-delete is always allowed; the only way to block it there is an Identity Platform blocking function. See REST: delete account.
OpenFirebase
When --check-with-auth succeeds (signup or sign-in), OpenFirebase automatically tests accounts:createAuthUri against the just-authenticated email and prints [FINDING] Email enumeration protection is DISABLED ... if the response leaks registered / signinMethods.
OpenFirebase only exercises email/password signup and anonymous auth. Other identity providers (Google, Apple, Facebook, GitHub, Microsoft, SAML, OIDC, phone) are not tested automatically.
# Tries email/password signup, falls back to anonymous, then retries protected 401/403 read/write checks with the obtained tokenopenfirebase --project-id PROJECT_ID --read-all --check-with-auth \ --email pentester@example.com --password 'SecurePass123!' \ --api-key AIza...
API keys can have restrictions applied to them (if you try to signup and it is restricted then the verbose response will tell you so). These restrictions are enforced server-side by header / IP matching with no cryptographic binding, so all client-side restrictions can be spoofed:
Restriction type
OpenFirebase flag(s)
Android app
--cert-sha1 <SHA1> --package-name com.example.app
iOS app
--ios-bundle-id com.example.app
HTTP referrer
--referer https://app.example.com/
IP address
Not bypassable from outside — requires source IP control (e.g. SSRF or a compromised host in scope)
Report when:
Anonymous or open email/password or identity provider signup is enabled and that token unlocks data/actions in RTDB, Firestore, Storage, or Functions that should be restricted.
Anonymous auth alone, with no downstream impact, is informational.
Email enumeration protection is disabled on the project (accounts:createAuthUri returns registered / signinMethods). This is a finding on its own — an attacker can validate arbitrary emails as users of the app.
The application has no UI for users to delete their own account, but accounts:delete still succeeds with a user ID token. The Enable delete toggle should be off; leaving it on contradicts the product's intent and lets an attacker with a stolen token / XSS / phishing primitive destroy victim accounts.
What it is: Firebase Realtime Database is a cloud-hosted JSON tree that clients read and write to directly from the app over a WebSocket/REST API — no backend server needed. It's Firebase's original database (predates Firestore).
RTDB security is governed by Realtime Database Rules. Any path is queryable by appending .json.
200 with null → public but root empty (still public; check known paths)
401 → rules require auth (not public)
404 → no database at this name
423 → database deactivated
Unauthenticated write (POST creates a new push-ID child, won't clobber existing data)
POST /openfirebase-unauth-check.json HTTP/1.1Host: PROJECT_ID.firebaseio.comContent-Type: application/json{"unauth_access":"OpenFirebase_write_check"}
A 200 response containing {"name":"-N..."} confirms the write succeeded — re-read the same path to verify.
Authenticated read/write with a token from §3 — repeat the root GET (optionally with ?shallow=true) and the POST above with ?auth=<idToken> appended. Then create a second account and try to read/write paths that belong to the first account (/users/<uid1>.json from user 2). Common rule mistake: .read: auth != null exposes everything to any signed-in user.
Secondary databases in the same project. Firebase allows multiple RTDB instances per project; the second one defaults to the project ID as its name (no -default-rtdb suffix). Try:
What it is: Cloud Firestore is Firebase's document-oriented NoSQL database. Data lives in hierarchical collections and documents, queried directly from clients via the SDK or REST.
Firestore is governed by Cloud Firestore Security Rules. Unlike RTDB, Firestore rules are not hierarchical — a rule on /users/{id} does not protect /users/{id}/private/{doc} unless explicitly written.
List a known collection unauthenticated
GET https://firestore.googleapis.com/v1/projects/PROJECT_ID/databases/(default)/documents/COLLECTION
200 with documents → public read
200 empty {} → database is public but collection is empty/nonexistent! Fuzz for common collections!
403 → rules deny
404 → no Firestore database in this project
Fuzz common collection names against any project that returned 200
Write a test document
POST /v1/projects/PROJECT_ID/databases/(default)/documents/firestore_unauthenticated_access{"fields":{"title":{"stringValue":"unauth_write_check"}}}
Subcollection traversal (e.g. users/{uid}/private) — common rules-bypass
Authenticated read/write with token from §3 — can you read another user's doc?
Named (non-default) databases — Firestore now supports multiple databases per project (docs)
What it is: Cloud Storage for Firebase is a thin wrapper around a Google Cloud Storage bucket, letting mobile/web clients upload and download files (images, video, documents) directly from the app. Each Firebase project gets a default bucket at PROJECT_ID.appspot.com (older) or PROJECT_ID.firebasestorage.app (newer).
Storage is governed by Storage Security Rules. The Firebase Storage REST API and the underlying GCS bucket are different surfaces.
400 → rules v1, listing disallowed (individual object reads may still work)
403 → permission denied
412 → service account missing permission
Read a known object name unauthenticated. Even when listing is denied (rules v1 returns 400, rules v2 may deny list while allowing get), individual objects can still be public. Source object names from APK strings, JS bundles, leaked URLs, or guessable patterns (avatars/<uid>.jpg, invoices/<id>.pdf, backups/db.sql). The path must be URL-encoded (/ → %2F):
GET /v0/b/PROJECT_ID.appspot.com/o/avatars%2F123.jpg?alt=media HTTP/1.1Host: firebasestorage.googleapis.com
?alt=media returns the file bytes; omit it to get the metadata JSON.
Upload a test file unauthenticated
POST /v0/b/PROJECT_ID.appspot.com/o?name=openfirebase_write_check.txt HTTP/1.1Host: firebasestorage.googleapis.comContent-Type: text/plainOpenFirebase - Unauth Firebase write access found
A 200 with a JSON body describing the uploaded object confirms the write. Re-fetch the object to verify.
Repeat the upload with a token from §3 (Authorization: Bearer <idToken>) and check whether an authenticated user can write to paths that should be restricted (e.g. /users/<other_uid>/...).
Underlying Google Cloud Storage bucket — same data, different access system. Firebase Storage Rules govern firebasestorage.googleapis.com; GCS IAM governs storage.googleapis.com. A bucket can be locked on one and public on the other (e.g. allUsers granted roles/storage.objectViewer directly in GCP IAM). Probe both endpoints:
GET /storage/v1/b/PROJECT_ID.appspot.com/o HTTP/1.1Host: storage.googleapis.comGET /PROJECT_ID.appspot.com/path/to/object.pdf HTTP/1.1Host: storage.googleapis.com
200 from either is a finding (and vice versa: a 200 only on firebasestorage.googleapis.com means Firebase rules are loose but IAM is fine). OpenFirebase tests both surfaces under --read-storage / --write-storage automatically.
What it is: Firebase Remote Config is a server-delivered key/value store that apps fetch at runtime to toggle features, change copy, or tune values without shipping a new release. Values can be targeted to user segments, A/B tests, or app versions.
Remote Config is fetched at:
POST https://firebaseremoteconfig.googleapis.com/v1/projects/PROJECT_ID/namespaces/firebase:fetch?key=API_KEY
This endpoint is designed to be called from clients, so a 200 is expected. The finding is in what the response contains.
Fetch Remote Config with extracted API key + App ID
Review parameters for: API keys, secrets, internal URLs, feature flags revealing unreleased features, debug toggles, JWT signing material. Scan the remote config with Betterleaks and Trufflehog.
Bypass API key restrictions by spoofing the identity the key is locked to. Google validates these server-side via headers with no cryptographic binding, so any restriction whose inputs come from the client can be forged:
Android app — send X-Android-Package: com.example.app + X-Android-Cert: <SHA1> (both extracted from the APK)
iOS app — send X-Ios-Bundle-Identifier: com.example.app (from the IPA Info.plist)
HTTP referrer — send Referer: https://app.example.com/ (allowed origin from the web bundle)
Remote Config exposes secrets, credentials, internal hostnames, or unreleased-feature flags. Simply being able to fetch Remote Config is never a finding!
What it is: Cloud Functions for Firebase lets you run Node.js / Python / Go backend code in response to Firebase events (Auth, Firestore, Storage, Pub/Sub) or HTTPS requests. Two trigger flavours matter for testing: HTTP triggers are raw HTTPS endpoints (any REST API), while callable functions use a Firebase-specific wire protocol over HTTPS. Comes in 1st gen (legacy, pure Cloud Functions) and 2nd gen (Cloud Run under the hood).
A Cloud Function has a different set of reachable URLs depending on whether it's 1st gen or 2nd gen. Gen1 has one URL. Gen2 has three. All three gen2 URLs hit the same function.
Running example throughout (all placeholder values):
<project-number> — numeric GCP project number, not the project ID. Same as App ID.
<region> — full region name (us-central1, europe-west1, ...)
Suffix is .run.app (no a.)
You can't tell from a URL alone whether it's an HTTP trigger or a callable — they share the hostname shape. The difference is: HTTP trigger accepts any method (probe GET, fall back to POST on 405/415); callable requires POST with body {"data": <payload>} and Content-Type: application/json. See callable-reference.
Enumerate function names and HTTP trigger URLs from APK/IPA strings and JS bundles.
Probe GCS source buckets across all regions for liveness and source-code leaks:
200 with an items array → source-code leak (can include env vars, Admin SDK keys, signing secrets)
401 → bucket exists but not publicly listable → "alive region" = functions are deployed here, worth probing invocation URLs
404 → no bucket → no functions deployed in that region
Same function name can run different code per region (us-central1/foo ≠ europe-west1/foo), so alive regions tell you where to enumerate. OpenFirebase does this automatically in extraction mode (project number is parsed from the app ID in the APK/IPA); use --function-region all if running in --project-id mode.
Probe HTTP triggers unauthenticated with GET, then POST {} with Content-Type: application/json on 405/415
Probe callables with POST body {"data": {}} and Content-Type: application/json. Add a idToken from §3 as Authorization: Bearer <token> to test authenticated paths.
Reading probe responses: treat 200/400/405/415/500 as "function reachable" — only a GCP front-door HTML body containing <title>404 Page not found</title> means the function doesn't exist; other 404s (e.g. Cannot GET /) mean it's reachable but the method/path was wrong.
App Check enforcement — is the function rejecting calls before the handler runs because an X-Firebase-AppCheck token is missing? You can't forge or replay these tokens from outside the app (they're short-lived JWTs minted by Google's attestation providers — Play Integrity, DeviceCheck, reCAPTCHA Enterprise — and signed by Google), so this is an inference, not a bypass. Pattern: send the request with a valid Firebase Auth Authorization: Bearer <idToken> from §3 and no App Check header. A 401 with body {"error": {"status": "UNAUTHENTICATED", ...}} means a valid Auth token wasn't enough to get past the gate — App Check is almost certainly enforced. OpenFirebase does exactly this check for callables on cloudfunctions.net when --check-with-auth is set.
Standard web testing on each reachable endpoint: IDOR, SSRF, injection, mass assignment, broken access control. OpenFirebase sends {"data": {}}; real exploitation needs handler-specific arguments. The response will probably tell you which parameters are missing. In that case it is also recommended to check the APK/IPA source code.
OpenFirebase
openfirebase -f app.apk --read-functions# Function names are extracted from the APK/IPA by default but more can be fuzzed with the added wordlistopenfirebase -f app.apk --read-functions --fuzz-functions ./openfirebase/wordlist/cloud-functions.txt# Direct project-ID mode (requires --function-name or --fuzz-functions)openfirebase --project-id PROJECT_ID --read-functions \ --function-name sendEmail,createUser --function-region all
Report when:
An unauthenticated invocation performs a privileged action or returns data the caller should not see, or
An authenticated call lets a signed-in user read or mutate data belonging to another user — e.g. the function trusts a user-supplied uid / record ID in the request body instead of req.auth.uid, or enforces "must be logged in" but never checks ownership. Test by creating two accounts and having user A request user B's resources via the callable, or
A GCS source bucket is publicly listable (source-code leak), or
Standard web vulns (IDOR/SSRF/injection/etc.) are found in function logic (OpenFirebase does not check if parameters are vulnerable to web vulns), or
App Check is not enabled on a function where Firebase recommends it (see App Check docs). This is a defense-in-depth gap, not a direct exploit.
What it is: Firebase Hosting serves static web content (HTML, JS, CSS, assets, SPAs) from Google's CDN on *.web.app and *.firebaseapp.com. A firebase.json config controls rewrites, redirects, and headers, and can proxy paths to Cloud Functions or Cloud Run.
Not supported by OpenFirebase — manual.
Check for exposed firebase.json and review rewrites, headers, redirects
Source maps (*.js.map) served in production
Preview channels (https://PROJECT_ID--<channel>-<hash>.web.app) leaking unreleased features
Rewrites that proxy to Cloud Functions / Cloud Run — test the rewritten path the same as §8
Report when: a hosting misconfiguration leaks source, secrets, or exposes a function/route that was not meant to be public.
A service account is a non-human Google/Firebase identity that applications use to authenticate with Firebase programmatically, using a private key rather than a password. A project can have multiple service accounts, each with different IAM roles and permission scopes.
If a service account private key is found embedded in an APK/IPA, public repo, or JS bundle — either as a raw JSON blob ("type": "service_account") or as hardcoded individual fields (-----BEGIN RSA PRIVATE KEY----- alongside an iam.gserviceaccount.com email) — the impact depends on what roles that account has been granted. It doesn't have to be the Admin SDK service account to be a valid finding. That said, if it is the Admin SDK account (firebase-adminsdk-xxxxx@PROJECT_ID), it bypasses all security rules and gives broad read/write access across all Firebase services.
Determine actual scope — use FireSA --check-permissions (see below)
Confirm impact on RTDB, Firestore, Storage, and Auth (list/delete/create users, databases, storage files etc)
OpenFirebase
# Found automatically during APK extraction; can also be provided manually:openfirebase --project-id PROJECT_ID \ --service-account firebase-adminsdk-xxxxx@PROJECT_ID.iam.gserviceaccount.com \ --private-key ./key.pem --read-all
Results authenticated via service account are tagged PUBLIC_SA in OpenFirebase output.
FireSA can also enumerate users, RTDB, Firestore, Storage, and IAM directly with flags like --list-all, --list-users, --list-iam, and supports user create/update/delete for impact demonstration.
Report when: the private key authenticates successfully and grants any access beyond what a normal user has. This is almost always Critical.
What it is: Firebase Cloud Messaging is Google's push-notification service. Apps register a device token with FCM; a backend then posts messages to Google and Google delivers them to the device.
The historically interesting endpoint was the legacy FCM server key API (https://fcm.googleapis.com/fcm/send), which authenticated with a static server key. Google announced discontinuation of the legacy HTTP and XMPP APIs in 2023 and shut the endpoints down in 2024. It is not a live surface in 2026.
The modern HTTP v1 API requires an OAuth 2.0 access token minted from a service account — i.e. FCM abuse is downstream of a §10 service-account compromise, not its own finding.