FIREBASE / FIRESTORE — ADVERSARIAL TESTING AND EXPLOITATION
Most impactful findings in Firebase apps arise from weak Firestore/Realtime Database rules, Cloud Storage exposure, callable/onRequest Functions trusting client input, incorrect ID token validation, and over-trusted App Check. Treat every client-supplied field and token as untrusted. Bind subject/tenant on the server, not in the client.
- Firestore (documents/collections, rules, REST/SDK)
- Realtime Database (JSON tree, rules)
- Cloud Storage (rules, signed URLs)
- Auth (ID tokens, custom claims, anonymous/sign-in providers)
- Cloud Functions (onCall/onRequest, triggers)
- Hosting rewrites, CDN/caching, CORS
- App Check (attestation) and its limits
1. Extract project config from client (apiKey, authDomain, projectId, appId, storageBucket, messagingSenderId). Identify all used Firebase products.
2. Obtain multiple principals: unauth, anonymous (if enabled), basic user A, user B, and any staff/admin if available. Capture their ID tokens.
3. Build Resource × Action × Principal matrix across Firestore/Realtime/Storage/Functions. Exercise every action via SDK and raw REST (googleapis) to detect parity gaps.
4. Start from list/query paths (where allowed) to seed IDs; then swap document paths, tenants, and user IDs across principals and transports.
- Firestore REST: https://firestore.googleapis.com/v1/projects//databases/(default)/documents/
- Storage REST: https://storage.googleapis.com/storage/v1/b/
- Auth: Google-signed ID tokens (iss accounts.google.com/securetoken.google.com/), aud ; identity is in sub/uid.
- Rules engines: separate for Firestore, Realtime DB, and Storage; Functions bypass rules when using Admin SDK.
- ID token verification must enforce issuer, audience (project), signature (Google JWKS), expiration, and optionally App Check binding when used.
- Custom claims are appended by Admin SDK; client-supplied claims are ignored by Auth but may be trusted by app code if copied into docs.
- Pitfalls:
- Accepting any JWT with valid signature but wrong audience/project.
- Trusting uid/account IDs from request body instead of context.auth.uid in Functions.
- Mixing session cookies and ID tokens without verifying both paths equivalently.
- Tests:
- Replay tokens across environments/projects; expect strict aud/iss rejection server-side.
- Call Functions with and without Authorization; verify identical checks on both onCall and onRequest variants.
- Rules are not filters: a query must include constraints that make the rule true for all returned documents; otherwise reads fail. Do not rely on client to include where clauses correctly.
- Prefer ownership derived from request.auth.uid and server data, not from client payload fields.
- Common gaps:
- allow read: if request.auth != null (any user reads all data)
- allow write: if request.auth != null (mass write)
- Missing per-field validation (adds isAdmin/role/tenantId fields).
- Using client-supplied ownerId/orgId instead of enforcing doc.ownerId == request.auth.uid or membership in org.
- Over-broad list rules on root collections; per-doc checks exist but list still leaks via queries.
- Validation patterns:
- Restrict writes: request.resource.data.keys().hasOnly([...]) and forbid privilege fields.
- Enforce ownership: resource.data.ownerId == request.auth.uid && request.resource.data.ownerId == request.auth.uid
- Org membership: exists(/databases/(default)/documents/orgs/$(org)/members/$(request.auth.uid))
- Tests:
- Compare results for users A/B on identical queries; diff counts and IDs.
- Attempt cross-tenant reads: where orgId == otherOrg; try queries without org filter to confirm denial.
- Write-path: set/patch with foreign ownerId/orgId; attempt to flip privilege flags.
- Enumerate via REST to avoid SDK client-side constraints; try structured and REST filters.
- Probe composite index requirements: UI-driven queries may hide missing rule coverage when indexes are enabled but rules are broad.
- Explore collection group queries (collectionGroup) that may bypass per-collection rules if not mirrored.
- Use startAt/endAt/in/array-contains to probe rule edges and pagination cursors for cross-tenant bleed.
- Misconfigured rules frequently expose entire JSON trees. Probe https://.firebaseio.com/.json with and without auth.
- Confirm rules for read/write use auth.uid and granular path checks; avoid .read/.write: true or auth != null at high-level nodes.
- Attempt to write privilege-bearing nodes (roles, org membership) and observe downstream effects (e.g., Cloud Functions triggers).
- Rules parallel Firestore but apply to object paths. Common issues:
- Public reads on sensitive buckets/paths.
- Signed URLs with long TTL, no content-disposition controls; replayable across tenants.
- List operations exposed: /o?prefix= enumerates object keys.
- Tests:
- GET gs:// paths via https endpoints without auth; verify content-type and Content-Disposition: attachment.
- Generate and reuse signed URLs across accounts and paths; try case/URL-encoding variants.
- Upload HTML/SVG and verify X-Content-Type-Options: nosniff; check for script execution.
- onCall provides context.auth automatically; onRequest must verify ID tokens explicitly. Admin SDK bypasses rules; all ownership/tenant checks must be enforced in code.
- Common gaps:
- Trusting client uid/orgId from request body instead of context.auth.
- Missing aud/iss verification when manually parsing tokens.
- Over-broad CORS allowing credentialed cross-origin requests; echoing Authorization in responses.
- Triggers (onCreate/onWrite) granting roles or issuing signed URLs solely based on document content controlled by the client.
- Tests:
- Call both onCall and equivalent onRequest endpoints with varied tokens and bodies; expect identical decisions.
- Create crafted docs to trigger privilege-granting functions; verify that server re-derives subject/tenant before acting.
- Attempt internal fetches (SSRF) via Functions to project/metadata endpoints.
- App Check is not a substitute for authorization. Many apps enable App Check enforcement on client SDKs but do not verify on custom backends.
- Bypasses:
- Unenforced paths: REST calls directly to googleapis endpoints with ID token succeed regardless of App Check.
- Mobile reverse engineering: hook client and reuse ID token flows without attestation.
- Tests:
- Compare SDK vs REST behavior with/without App Check headers; confirm no elevated authorization via App Check alone.
- Apps often implement multi-tenant data models (orgs//...). Bind tenant from server context (membership doc or custom claim), not from client payload.
- Tests:
- Vary org header/subdomain/query while keeping token fixed; verify server denies cross-tenant access.
- Export/report Functions: ensure queries execute under caller scope; signed outputs must encode tenant and short TTL.
- Content-type switching: JSON vs form vs multipart to hit alternate code paths in onRequest Functions.
- Parameter/field pollution: duplicate JSON keys; last-one-wins in many parsers; attempt to sneak privilege fields.
- Caching/CDN: Hosting rewrites or proxies that key responses without Authorization or tenant headers.
- Race windows: write then read before background enforcements (e.g., post-write claim synchronizations) complete.
- Firestore: use error shape, document count, and ETag/length to infer existence under partial denial.
- Storage: length/timing differences on signed URL attempts leak validity.
- Functions: constant-time comparisons vs variable messages reveal authorization branches.
- SDK + REST: httpie/curl + jq for REST; Firebase emulator and Rules Playground for rapid iteration.
- Mobile: apktool/objection/frida to extract config and hook SDK calls; inspect network logs for endpoints and tokens.
- Rules analysis: script rule probes for common patterns (auth != null, missing field validation, list vs get parity).
- Functions: fuzz onRequest endpoints with varied content-types and missing/forged Authorization; verify CORS and token handling.
- Storage: enumerate prefixes; test signed URL generation and reuse patterns.
- Do Firestore/Realtime/Storage rules derive subject and tenant from auth, not client fields?
- Are list/query rules aligned with per-doc checks (no broad list leaks)?
- Are privilege-bearing fields immutable or server-only (forbidden in writes)?
- Do Functions verify ID tokens (iss/aud/exp/signature) and re-derive identity before acting?
- Are Admin SDK operations scoped by server-side checks (ownership/tenant)?
- Is App Check treated as advisory, not authorization, across all paths?
- Are Hosting/CDN cache keys bound to Authorization/tenant to prevent leaks?
1. Provide owner vs non-owner Firestore queries showing unauthorized access or metadata leak.
2. Demonstrate Cloud Storage read/write beyond intended scope (public object, signed URL reuse, or list exposure).
3. Show a Function accepting forged/foreign identity (wrong aud/iss) or trusting client uid/orgId.
4. Document minimal reproducible requests with roles/tokens used and observed deltas.
- Public collections/objects documented and intended.
- Rules that correctly enforce per-doc checks with matching query constraints.
- Functions verifying tokens and ignoring client-supplied identifiers.
- App Check enforced but not relied upon for authorization.
- Cross-account and cross-tenant data exposure.
- Unauthorized state changes via Functions or direct writes.
- Exfiltration of PII/PHI and private files from Storage.
- Durable privilege escalation via misused custom claims or triggers.
1. Treat apiKey as project identifier only; identity must come from verified ID tokens.
2. Start from rules: read them, then prove gaps with diffed owner/non-owner requests.
3. Prefer REST for parity checks; SDKs can mask errors via client-side filters.
4. Hunt privilege fields in docs and forbid them via rules; verify immutability.
5. Probe collectionGroup queries and list rules; many leaks live there.
6. Functions are the authority boundary—enforce subject/tenant there even if rules exist.
7. Keep concise PoCs: one owner vs non-owner request per surface that clearly demonstrates the unauthorized delta.
Authorization must hold at every layer: rules, Functions, and Storage. Bind subject and tenant from verified tokens and server data, never from client payload or UI assumptions. Any gap becomes a cross-account or cross-tenant vulnerability.