first commit
This commit is contained in:
@@ -0,0 +1,211 @@
|
||||
---
|
||||
name: firebase-firestore
|
||||
description: Firebase/Firestore security testing covering security rules, Cloud Functions, and client-side trust issues
|
||||
---
|
||||
|
||||
# Firebase / Firestore
|
||||
|
||||
Security testing for Firebase applications. Focus on Firestore/Realtime Database rules, Cloud Storage exposure, callable/onRequest Functions trusting client input, and incorrect ID token validation.
|
||||
|
||||
## Attack Surface
|
||||
|
||||
**Data Stores**
|
||||
- Firestore (documents/collections, rules, REST/SDK)
|
||||
- Realtime Database (JSON tree, rules)
|
||||
- Cloud Storage (rules, signed URLs)
|
||||
|
||||
**Authentication**
|
||||
- Auth ID tokens, custom claims, anonymous/sign-in providers
|
||||
- App Check attestation (and its limits)
|
||||
|
||||
**Server-Side**
|
||||
- Cloud Functions (onCall/onRequest, triggers)
|
||||
- Admin SDK (bypasses rules)
|
||||
|
||||
**Infrastructure**
|
||||
- Hosting rewrites, CDN/caching, CORS
|
||||
|
||||
## Architecture
|
||||
|
||||
**Endpoints**
|
||||
- Firestore REST: `https://firestore.googleapis.com/v1/projects/<project>/databases/(default)/documents/<path>`
|
||||
- Realtime DB: `https://<project>.firebaseio.com/.json`
|
||||
- Storage REST: `https://storage.googleapis.com/storage/v1/b/<bucket>`
|
||||
|
||||
**Auth**
|
||||
- Google-signed ID tokens (iss: `accounts.google.com` or `securetoken.google.com/<project>`)
|
||||
- Audience: `<project>` or `<app-id>`, identity in `sub`/`uid`
|
||||
- Rules engines: separate for Firestore, Realtime DB, and Storage
|
||||
- Functions bypass rules when using Admin SDK
|
||||
|
||||
## High-Value Targets
|
||||
|
||||
- Firestore collections with sensitive data (users, orders, payments)
|
||||
- Realtime Database root and high-level nodes
|
||||
- Cloud Storage buckets with private files
|
||||
- Cloud Functions (especially triggers that grant roles or issue signed URLs)
|
||||
- Admin/staff routes and privilege-granting endpoints
|
||||
- Export/report functions that generate signed outputs
|
||||
|
||||
## Reconnaissance
|
||||
|
||||
**Extract Project Config**
|
||||
|
||||
From client bundle:
|
||||
```javascript
|
||||
// apiKey, authDomain, projectId, appId, storageBucket, messagingSenderId
|
||||
firebase.apps[0].options
|
||||
```
|
||||
|
||||
**Obtain Principals**
|
||||
- Unauthenticated
|
||||
- Anonymous (if enabled)
|
||||
- Basic user A, user B
|
||||
- Staff/admin (if available)
|
||||
|
||||
Capture ID tokens for each.
|
||||
|
||||
## Key Vulnerabilities
|
||||
|
||||
### Firestore Rules
|
||||
|
||||
Rules are not filters—a query must include constraints that make the rule true for all returned documents.
|
||||
|
||||
**Common Gaps**
|
||||
- `allow read: if request.auth != null` — any authenticated user reads all data
|
||||
- `allow write: if request.auth != null` — mass write access
|
||||
- Missing per-field validation (allows adding `isAdmin`/`role`/`tenantId` fields)
|
||||
- Using client-supplied `ownerId`/`orgId` instead of `resource.data.ownerId == request.auth.uid`
|
||||
- Over-broad list rules on root collections (per-doc checks exist but list still leaks)
|
||||
|
||||
**Secure Patterns**
|
||||
```javascript
|
||||
// Restrict write fields
|
||||
request.resource.data.keys().hasOnly(['field1', 'field2', 'field3'])
|
||||
|
||||
// Enforce ownership
|
||||
resource.data.ownerId == request.auth.uid &&
|
||||
request.resource.data.ownerId == request.auth.uid
|
||||
|
||||
// Org membership check
|
||||
exists(/databases/(default)/documents/orgs/$(org)/members/$(request.auth.uid))
|
||||
```
|
||||
|
||||
**Tests**
|
||||
- Compare results for users A/B on identical queries; diff counts and IDs
|
||||
- Cross-tenant reads: `where orgId == otherOrg`; try queries without org filter
|
||||
- Write-path: set/patch with foreign `ownerId`/`orgId`; attempt to flip privilege flags
|
||||
|
||||
### Firestore Queries
|
||||
|
||||
- Use REST to avoid SDK client-side constraints
|
||||
- Probe composite index requirements (UI-driven queries may hide missing rule coverage)
|
||||
- Explore `collectionGroup` queries that may bypass per-collection rules
|
||||
- Use `startAt`/`endAt`/`in`/`array-contains` to probe rule edges and pagination cursors
|
||||
|
||||
### Realtime Database
|
||||
|
||||
- Misconfigured rules frequently expose entire JSON trees
|
||||
- Probe `https://<project>.firebaseio.com/.json` with and without auth
|
||||
- Confirm rules 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)
|
||||
|
||||
### Cloud Storage
|
||||
|
||||
**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 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
|
||||
|
||||
### Cloud Functions
|
||||
|
||||
`onCall` provides `context.auth` automatically; `onRequest` must verify ID tokens explicitly. Admin SDK bypasses rules—all ownership/tenant checks must be 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
|
||||
- Triggers (onCreate/onWrite) granting roles based on document content controlled by client
|
||||
|
||||
**Tests**
|
||||
- Call both onCall and onRequest endpoints with varied tokens; expect identical decisions
|
||||
- Create crafted docs to trigger privilege-granting functions
|
||||
- Attempt SSRF via Functions to project/metadata endpoints
|
||||
|
||||
### Auth & Token Issues
|
||||
|
||||
**Verification Requirements**
|
||||
- Issuer, audience (project), signature (Google JWKS), expiration
|
||||
- Optionally App Check binding when used
|
||||
|
||||
**Pitfalls**
|
||||
- Accepting any JWT with valid signature but wrong audience/project
|
||||
- Trusting `uid`/account IDs from request body instead of `context.auth.uid`
|
||||
- Mixing session cookies and ID tokens without verifying both paths equivalently
|
||||
- Custom claims copied into docs then trusted by app code
|
||||
|
||||
**Tests**
|
||||
- Replay tokens across environments/projects; expect strict `aud`/`iss` rejection
|
||||
- Call Functions with and without Authorization; verify identical checks
|
||||
|
||||
### App Check
|
||||
|
||||
App Check is not a substitute for authorization.
|
||||
|
||||
**Bypasses**
|
||||
- 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
|
||||
|
||||
### Tenant Isolation
|
||||
|
||||
Apps often implement multi-tenant data models (`orgs/<orgId>/...`). Bind tenant from server context (membership doc or custom claim), not 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
|
||||
|
||||
## Bypass Techniques
|
||||
|
||||
- Content-type switching: JSON vs form vs multipart to hit alternate code paths in onRequest
|
||||
- Parameter/field pollution: duplicate JSON keys (last-one-wins in many parsers); sneak privilege fields
|
||||
- Caching/CDN: Hosting rewrites keying responses without Authorization or tenant headers
|
||||
- Race windows: write then read before background enforcements complete
|
||||
|
||||
## Blind Enumeration
|
||||
|
||||
- Firestore: use error shape, document count, ETag/length to infer existence
|
||||
- Storage: length/timing differences on signed URL attempts leak validity
|
||||
- Functions: constant-time comparisons vs variable messages reveal authorization branches
|
||||
|
||||
## Testing Methodology
|
||||
|
||||
1. **Extract config** - Get project config from client bundle
|
||||
2. **Obtain principals** - Collect tokens for unauth, anonymous, user A/B, admin
|
||||
3. **Build matrix** - Resource × Action × Principal across Firestore/Realtime/Storage/Functions
|
||||
4. **SDK vs REST** - Exercise every action via both to detect parity gaps
|
||||
5. **Seed IDs** - Start from list/query paths to gather document IDs
|
||||
6. **Cross-principal** - Swap document paths, tenants, and user IDs across principals
|
||||
|
||||
## Tooling
|
||||
|
||||
- SDK + REST: httpie/curl + jq for REST; Firebase emulator and Rules Playground for rapid iteration
|
||||
- Rules analysis: script probes for common patterns (`auth != null`, missing field validation)
|
||||
- Functions: fuzz onRequest with varied content-types and missing/forged Authorization
|
||||
- Storage: enumerate prefixes; test signed URL generation and reuse patterns
|
||||
|
||||
## Validation Requirements
|
||||
|
||||
- Owner vs non-owner Firestore queries showing unauthorized access or metadata leak
|
||||
- Cloud Storage read/write beyond intended scope (public object, signed URL reuse, list exposure)
|
||||
- Function accepting forged/foreign identity (wrong `aud`/`iss`) or trusting client `uid`/`orgId`
|
||||
- Minimal reproducible requests with roles/tokens used and observed deltas
|
||||
@@ -0,0 +1,268 @@
|
||||
---
|
||||
name: supabase
|
||||
description: Supabase security testing covering Row Level Security, PostgREST, Edge Functions, and service key exposure
|
||||
---
|
||||
|
||||
# Supabase
|
||||
|
||||
Security testing for Supabase applications. Focus on mis-scoped Row Level Security (RLS), unsafe RPCs, leaked `service_role` keys, lax Storage policies, and Edge Functions trusting headers without binding to issuer/audience/tenant.
|
||||
|
||||
## Attack Surface
|
||||
|
||||
**Data Access**
|
||||
- PostgREST: table CRUD, filters, embeddings, RPC (remote functions)
|
||||
- GraphQL: pg_graphql over Postgres schema with RLS interaction
|
||||
- Realtime: replication subscriptions, broadcast/presence channels
|
||||
|
||||
**Storage**
|
||||
- Buckets, objects, signed URLs, public/private policies
|
||||
|
||||
**Authentication**
|
||||
- Auth (GoTrue): JWTs, cookie/session, magic links, OAuth flows
|
||||
|
||||
**Server-Side**
|
||||
- Edge Functions (Deno): server-side code calling Supabase with secrets
|
||||
|
||||
## Architecture
|
||||
|
||||
**Endpoints**
|
||||
- REST: `https://<ref>.supabase.co/rest/v1/<table>`
|
||||
- RPC: `https://<ref>.supabase.co/rest/v1/rpc/<fn>`
|
||||
- Storage: `https://<ref>.supabase.co/storage/v1`
|
||||
- GraphQL: `https://<ref>.supabase.co/graphql/v1`
|
||||
- Realtime: `wss://<ref>.supabase.co/realtime/v1`
|
||||
- Auth: `https://<ref>.supabase.co/auth/v1`
|
||||
- Functions: `https://<ref>.functions.supabase.co/`
|
||||
|
||||
**Headers**
|
||||
- `apikey: <anon-or-service>` — identifies project
|
||||
- `Authorization: Bearer <JWT>` — binds user context
|
||||
|
||||
**Roles**
|
||||
- `anon`, `authenticated` — standard roles
|
||||
- `service_role` — bypasses RLS, must never be client-exposed
|
||||
|
||||
**Key Principle**
|
||||
`auth.uid()` returns current user UUID from JWT. Policies must never trust client-supplied IDs over server context.
|
||||
|
||||
## High-Value Targets
|
||||
|
||||
- Tables with sensitive data (users, orders, payments, PII)
|
||||
- RPC functions (especially `SECURITY DEFINER`)
|
||||
- Storage buckets with private files
|
||||
- Edge Functions with `service_role` access
|
||||
- Export/report endpoints generating signed outputs
|
||||
- Admin/staff routes and privilege-granting endpoints
|
||||
|
||||
## Reconnaissance
|
||||
|
||||
**Enumerate Surfaces**
|
||||
```
|
||||
/rest/v1/<table>
|
||||
/rest/v1/rpc/<fn>
|
||||
/storage/v1/object/public/<bucket>/
|
||||
/storage/v1/object/list/<bucket>?prefix=
|
||||
/graphql/v1
|
||||
/auth/v1
|
||||
```
|
||||
|
||||
**Obtain Principals**
|
||||
- Unauthenticated (anon key only)
|
||||
- Basic user A, user B
|
||||
- Admin/staff (if available)
|
||||
- Check if `service_role` key leaked in client bundle or Edge Function responses
|
||||
|
||||
## Key Vulnerabilities
|
||||
|
||||
### Row Level Security (RLS)
|
||||
|
||||
Enable RLS on every non-public table; absence or "permit-all" policies → bulk exposure.
|
||||
|
||||
**Common Gaps**
|
||||
- Policies check `auth.uid()` for SELECT but forget UPDATE/DELETE/INSERT
|
||||
- Missing tenant constraints (`org_id`/`tenant_id`) allow cross-tenant access
|
||||
- Policies rely on client-provided columns (`user_id` in payload) instead of JWT
|
||||
- Complex joins where policy is applied after filters, enabling inference via counts
|
||||
|
||||
**Tests**
|
||||
```bash
|
||||
# Compare row counts for two users
|
||||
GET /rest/v1/<table>?select=*&Prefer=count=exact
|
||||
|
||||
# Cross-tenant probe
|
||||
GET /rest/v1/<table>?org_id=eq.<other_org>
|
||||
GET /rest/v1/<table>?or=(org_id.eq.other,org_id.is.null)
|
||||
|
||||
# Write-path
|
||||
PATCH /rest/v1/<table>?id=eq.<foreign_id>
|
||||
DELETE /rest/v1/<table>?id=eq.<foreign_id>
|
||||
POST /rest/v1/<table> with foreign owner_id
|
||||
```
|
||||
|
||||
### PostgREST & REST
|
||||
|
||||
**Filters**
|
||||
- `eq`, `neq`, `lt`, `gt`, `ilike`, `or`, `is`, `in`
|
||||
- Embed relations: `select=*,profile(*)`—exploits overfetch if resolvers skip per-row checks
|
||||
- Search leaks: generous `LIKE`/`ILIKE` filters combined with missing RLS → mass disclosure via wildcard queries
|
||||
|
||||
**Headers**
|
||||
- `Prefer: return=representation` — echo writes
|
||||
- `Prefer: count=exact` — exposure via counts
|
||||
- `Accept-Profile`/`Content-Profile` — select schema
|
||||
|
||||
**IDOR Patterns**
|
||||
```
|
||||
/rest/v1/<table>?select=*&id=eq.<other_id>
|
||||
/rest/v1/<table>?select=*&slug=eq.<other_slug>
|
||||
/rest/v1/<table>?select=*&email=eq.<other_email>
|
||||
```
|
||||
|
||||
**Mass Assignment**
|
||||
- If RPC not used, PATCH can update unintended columns
|
||||
- Verify restricted columns via database permissions/policies
|
||||
|
||||
### RPC Functions
|
||||
|
||||
RPC endpoints map to SQL functions. `SECURITY DEFINER` bypasses RLS unless carefully coded; `SECURITY INVOKER` respects caller.
|
||||
|
||||
**Anti-Patterns**
|
||||
- `SECURITY DEFINER` + missing owner checks → vertical/horizontal bypass
|
||||
- `set search_path` left to public; function resolves unsafe objects
|
||||
- Trusting client-supplied `user_id`/`tenant_id` rather than `auth.uid()`
|
||||
|
||||
**Tests**
|
||||
```bash
|
||||
# Call as different users with foreign IDs
|
||||
POST /rest/v1/rpc/<fn> {"user_id": "<foreign_id>"}
|
||||
|
||||
# Remove JWT entirely
|
||||
Authorization: Bearer <anon_token>
|
||||
```
|
||||
Verify functions perform explicit ownership/tenant checks inside SQL.
|
||||
|
||||
### Storage
|
||||
|
||||
**Buckets**
|
||||
- Public vs private; objects in `storage.objects` with RLS-like policies
|
||||
|
||||
**Misconfigurations**
|
||||
```bash
|
||||
# Public bucket with sensitive data
|
||||
GET /storage/v1/object/public/<bucket>/<path>
|
||||
|
||||
# List prefixes without auth
|
||||
GET /storage/v1/object/list/<bucket>?prefix=
|
||||
|
||||
# Signed URL reuse across tenants/paths
|
||||
```
|
||||
|
||||
**Content-Type Abuse**
|
||||
- Upload HTML/SVG served as `text/html` or `image/svg+xml`
|
||||
- Verify `X-Content-Type-Options: nosniff` and `Content-Disposition: attachment`
|
||||
|
||||
**Path Confusion**
|
||||
- Mixed case, URL-encoding, `..` segments may be rejected at UI but accepted by API
|
||||
- Test path normalization differences between client validation and server handling
|
||||
|
||||
### Realtime
|
||||
|
||||
**Endpoint**: `wss://<ref>.supabase.co/realtime/v1`
|
||||
|
||||
**Risks**
|
||||
- Channel names derived from table/schema/filters leaking other users' updates when RLS or channel guards are weak
|
||||
- Broadcast/presence channels allowing cross-room join/publish without auth
|
||||
|
||||
**Tests**
|
||||
- Subscribe to `public:realtime` changes on protected tables; confirm visibility aligns with RLS
|
||||
- Attempt joining other users' channels: `room:<user_id>`, `org:<org_id>`
|
||||
|
||||
### GraphQL
|
||||
|
||||
**Endpoint**: `/graphql/v1` using pg_graphql with RLS
|
||||
|
||||
**Risks**
|
||||
- Introspection reveals schema relations
|
||||
- Overfetch via nested relations where resolvers skip per-row ownership checks
|
||||
- Global node IDs leaked and reusable via different viewers
|
||||
|
||||
**Tests**
|
||||
- Compare REST vs GraphQL responses for same principal and query shape
|
||||
- Query deep nested fields; verify RLS holds at each edge
|
||||
|
||||
### Auth & Tokens
|
||||
|
||||
GoTrue issues JWTs with claims (`sub=uid`, `role`, `aud=authenticated`).
|
||||
|
||||
**Verification Requirements**
|
||||
- Issuer, audience, expiration, signature, tenant context
|
||||
|
||||
**Pitfalls**
|
||||
- Storing tokens in localStorage → XSS exfiltration
|
||||
- Treating `apikey` as identity (it's project-scoped, not user identity)
|
||||
- Exposing `service_role` key in client bundle or Edge Function responses
|
||||
- Refresh token mismanagement leading to long-lived sessions beyond intended TTL
|
||||
|
||||
**Tests**
|
||||
- Replay tokens across services; check audience/issuer pinning
|
||||
- Try downgraded tokens (expired/other audience) against custom endpoints
|
||||
|
||||
### Edge Functions
|
||||
|
||||
Deno-based functions often initialize Supabase client with `service_role`.
|
||||
|
||||
**Risks**
|
||||
- Trusting Authorization/apikey headers without verifying JWT against issuer/audience
|
||||
- CORS: wildcard origins with credentials; reflected Authorization in responses
|
||||
- SSRF via fetch; secrets exposed via error traces or logs
|
||||
|
||||
**Tests**
|
||||
- Call functions with and without Authorization; compare behavior
|
||||
- Try foreign resource IDs in payloads; verify server re-derives user/tenant from JWT
|
||||
- Attempt to reach internal endpoints (metadata services) via function fetch
|
||||
|
||||
### Tenant Isolation
|
||||
|
||||
Ensure every query joins or filters by `tenant_id`/`org_id` derived from JWT context, not client input.
|
||||
|
||||
**Tests**
|
||||
- Change subdomain/header/path tenant selectors while keeping JWT tenant constant
|
||||
- Export/report endpoints: confirm queries execute under caller scope
|
||||
|
||||
## Bypass Techniques
|
||||
|
||||
- Content-type switching: `application/json` ↔ `application/x-www-form-urlencoded` ↔ `multipart/form-data`
|
||||
- Parameter pollution: duplicate keys in JSON/query (PostgREST chooses last/first depending on parser)
|
||||
- GraphQL+REST parity probing: protections often drift; fetch via the weaker path
|
||||
- Race windows: parallel writes to bypass post-insert ownership updates
|
||||
|
||||
## Blind Enumeration
|
||||
|
||||
- Use `Prefer: count=exact` and ETag/length diffs to infer unauthorized rows
|
||||
- Conditional requests (`If-None-Match`) to detect object existence
|
||||
- Storage signed URLs: timing/length deltas to map valid vs invalid tokens
|
||||
|
||||
## Testing Methodology
|
||||
|
||||
1. **Inventory surfaces** - Map REST, Storage, GraphQL, Realtime, Auth, Functions endpoints
|
||||
2. **Obtain principals** - Collect tokens for anon, user A/B, admin; check for `service_role` leaks
|
||||
3. **Build matrix** - Resource × Action × Principal
|
||||
4. **REST vs GraphQL** - Test both to find parity gaps
|
||||
5. **Seed IDs** - Start with list/search endpoints to gather IDs
|
||||
6. **Cross-principal** - Swap IDs, tenants, and transports across principals
|
||||
|
||||
## Tooling
|
||||
|
||||
- PostgREST: httpie/curl + jq; enumerate tables; fuzz filters (`or=`, `ilike`, `neq`, `is.null`)
|
||||
- GraphQL: graphql-inspector, voyager; deep queries for field-level enforcement
|
||||
- Realtime: custom ws client; subscribe to suspicious channels; diff payloads per principal
|
||||
- Storage: enumerate bucket listing APIs; script signed URL patterns
|
||||
- Auth/JWT: jwt-cli/jose to validate audience/issuer; replay against Edge Functions
|
||||
- Policy diffing: maintain request sets per role; compare results across releases
|
||||
|
||||
## Validation Requirements
|
||||
|
||||
- Owner vs non-owner requests for REST/GraphQL showing unauthorized access (content or metadata)
|
||||
- Mis-scoped RPC or Storage signed URL usable by another user/tenant
|
||||
- Realtime or GraphQL exposure matching missing policy checks
|
||||
- Minimal reproducible requests with role contexts documented
|
||||
Reference in New Issue
Block a user