Webhook Abuse in Firestore
How Webhook Abuse Manifests in Firestore
Firestore itself does not have native webhooks; instead, it uses Cloud Functions triggers (e.g., functions.firestore.document(...).onWrite(...)) to react to database events. However, developers often expose these functions via HTTP triggers or create separate HTTP endpoints that mimic Firestore event structures, effectively turning them into webhooks. Attackers exploit these endpoints when they are improperly secured, leading to data theft, unauthorized operations, or resource exhaustion.
Common Firestore-specific attack patterns include:
- Unauthenticated Event Injection: An attacker directly calls an HTTP-triggered Cloud Function that expects a Firestore event payload. If the function does not validate the request origin (e.g., via Firebase Auth token or App Check), the attacker can forge a
datafield to perform arbitrary writes or reads, bypassing Firestore security rules because the function runs with admin privileges. - Event Payload Tampering: Firestore triggers pass an
eventobject containingbeforeandaftersnapshots. A vulnerable function might use these snapshots without verifying they came from Firestore. An attacker could inject a payload with{ "before": null, "after": { "fields": { "secret": { "stringValue": "leaked" } } } }to trick the function into processing unauthorized data. - Data Exfiltration via Response: Some functions return Firestore data in their HTTP response. If the endpoint lacks authentication, an attacker can query it to retrieve sensitive documents, effectively turning the function into a data proxy that circumvents Firestore rules.
- Replay Attacks: Firestore events include timestamps and document paths. An attacker who captures a legitimate event could replay it to trigger duplicate actions (e.g., creating orders), especially if the function does not check for idempotency or event freshness.
For example, consider a Cloud Function meant to sync Firestore changes to an external system:
exports.syncToExternal = functions.https.onRequest(async (req, res) => {
const doc = req.body.after;
// VULNERABLE: No verification that req came from Firestore
await externalApi.post('/data', doc);
res.status(200).send('OK');
});If this endpoint is publicly accessible, an attacker can send a crafted POST request with any Firestore document structure, causing the function to write arbitrary data to the external system.
Firestore-Specific Detection
To identify webhook abuse risks in Firestore-related endpoints, look for:
- Missing Authentication on HTTP Triggers: Check if Cloud Functions with
functions.https.onRequestenforce Firebase ID token verification or App Check. A function that only checksreq.method === 'POST'is vulnerable. - Overly Permissive Security Rules: Firestore rules cannot protect HTTP-triggered functions. However, if a function reads/writes Firestore based on user-provided IDs without validating ownership, it may violate BOLA/IDOR (OWASP API Top 10:2023 #4). For example:
// VULNERABLE RULE in firestore.rules
match /users/{userId}/messages/{msgId} {
allow read: if request.auth != null; // But function uses admin SDK bypassing this
}
// The function may still expose other users' messages if it uses req.query.msgId- Event Source Validation Gaps: Verify that functions triggered by Firestore events (e.g.,
onCreate) are not also exposed via HTTP. If they are, ensure the HTTP version checks thefirebase-authheader orX-Firebase-GCheck(App Check) header. - Data Exposure in Responses: Test if the endpoint returns full document snapshots. Use a tool like middleBrick to submit the function URL. The scanner will flag unauthenticated endpoints and analyze responses for PII, API keys, or excessive data. For instance, a response like
{ "doc": { "email": "[email protected]", "ssn": "123-45-6789" } }indicates Data Exposure (OWASP API Top 10:2023 #8).
Scanning with middleBrick: Submit the Cloud Function URL (e.g., https://us-central1-project.cloudfunctions.net/syncToExternal) to the web dashboard or CLI (middlebrick scan <url>). The scanner tests for missing authentication, input validation flaws (e.g., does it accept arbitrary document paths?), and data leakage in responses. It also cross-references any OpenAPI spec you provide to see if the endpoint is documented as requiring auth.
Firestore-Specific Remediation
Fix webhook abuse in Firestore by securing the Cloud Function layer and validating event authenticity:
- Enforce Firebase App Check for HTTP Triggers: App Check ensures requests come from your registered apps (web, iOS, Android). For HTTP functions, validate the App Check token:
const { initializeApp } = require('firebase-admin/app');
const { getAppCheck } = require('firebase-admin/app-check');
initializeApp();
exports.secureSync = functions.https.onRequest(async (req, res) => {
const appCheckToken = req.get('X-Firebase-GCheck');
if (!appCheckToken) {
return res.status(401).send('Missing App Check token');
}
try {
await getAppCheck().verifyToken(appCheckToken);
} catch (error) {
return res.status(401).send('Invalid App Check token');
}
// Now process req.body, but still validate structure
const expectedSchema = { after: { fields: { /* ... */ } } };
// ... rest of logic
});- Validate Firestore Event Structure: If the function is triggered by Firestore (
onCreate), do not also expose it via HTTP. If you need an HTTP endpoint, separate concerns: one function for Firestore triggers (no HTTP), another for webhooks with strict schema validation. Use a library likeajvto validate the incoming payload against a JSON schema that matches Firestore's event format.
const Ajv = require('ajv');
const ajv = new Ajv();
const firestoreEventSchema = {
type: 'object',
properties: {
before: { type: ['object', 'null'] },
after: { type: 'object' },
},
required: ['after'],
};
exports.validatedWebhook = functions.https.onRequest(async (req, res) => {
const valid = ajv.validate(firestoreEventSchema, req.body);
if (!valid) {
return res.status(400).json({ error: 'Invalid event format', details: ajv.errors });
}
// Proceed with req.body.after
});- Restrict Firestore Access in Functions: Even with admin privileges, functions should not expose all data. Use document-level checks:
exports.userSpecificAction = functions.https.onCall(async (data, context) => {
// For callable functions, context.auth is available
if (!context.auth) {
throw new functions.https.HttpsError('unauthenticated', 'Auth required');
}
const userId = context.auth.uid;
const docSnap = await admin.firestore().collection('users').doc(userId).get();
// Only return data the user owns
return { email: docSnap.data().email };
});- Use Security Rules to Limit Function Access: While rules don't apply to admin SDK, you can restrict which documents a function can access by using a service account with limited permissions. Create a custom IAM role for the Cloud Function's service account that only allows
datastore.entities.geton specific collections.
Additionally, monitor logs for unexpected function invocations. Set up Cloud Audit Logs to alert on google.cloud.functions.v1.CloudFunctionsService.UpdateFunction or high-volume calls to the endpoint.
For continuous validation, integrate middleBrick into your CI/CD pipeline via the GitHub Action. On every pull request, scan new or modified Cloud Function URLs to ensure they meet authentication and data exposure checks before deployment.
Frequently Asked Questions
How is a Firestore trigger different from an HTTP webhook, and does middleBrick scan both?
functions.firestore.document('users/{id}').onWrite(...)) is invoked automatically by Firestore events and has no public URL. An HTTP webhook is a separately deployed Cloud Function with functions.https.onRequest that accepts public POST requests. middleBrick scans any accessible URL, so it can test HTTP-triggered functions but cannot directly scan Firestore triggers since they lack an endpoint. However, if you expose a Firestore-triggered function's logic via an HTTP wrapper, middleBrick will scan that wrapper endpoint for authentication and data exposure flaws.