Nosql Injection in Firestore
How Nosql Injection Manifests in Firestore
Firestore NoSQL injection occurs when user‑supplied data is incorporated into a query without proper validation, allowing an attacker to alter the query’s logic. Unlike SQL, Firestore uses a structured API where the field path, comparison operator, and value are separate arguments. If any of these arguments can be controlled by the user, the attacker can change what data is read or written.
Common vulnerable patterns in Firestore client code include:
- Dynamic operator injection – the comparison operator (e.g., ==, >=, array-contains) is taken from user input and passed directly to
where. - Field path manipulation – the field name used in
whereororderByis built from user input, allowing the attacker to target unintended fields (e.g.,__name__or internal metadata). - Value‑based injection via arrays or maps – when a user‑provided value is an object or array, malicious nested fields can change the query’s semantics (e.g., injecting a
FieldValue.delete()sentinel).
Example of a vulnerable Node.js function using the Firebase Admin SDK:
const { getFirestore, collection, query, where } = require('firebase-admin/firestore');
async function getPostsByAuthor(req, res) {
const db = getFirestore();
const { authorId, op } = req.query; // both come from the client
// No validation – op could be '>=', '<=', 'array-contains', etc.
const q = query(collection(db, 'posts'), where('authorId', op, authorId));
const snapshot = await getDocs(q);
res.json(snapshot.docs.map(doc => doc.data()));
}
If an attacker supplies op=array-contains and authorId=["admin"], the query becomes where('authorId', 'array-contains', ['admin']), which matches any document where the authorId field is an array containing the string "admin". By carefully choosing operators and values, an attacker can bypass intended filters, read unauthorized data, or cause denial‑of‑service by forcing expensive scans.
Firestore security rules can mitigate the impact, but if the application logic allows the injection to happen before rules are evaluated, the attacker may still access data that the rules would otherwise block.
Firestore-Specific Detection
Detecting NoSQL injection in Firestore requires examining how user data flows into query builders. Static analysis can flag calls to where, orderBy, startAt, endAt, or limit where arguments originate from request parameters, headers, or body without validation. Dynamic testing (black‑box) sends crafted payloads to endpoints and observes whether the response changes in a way that indicates altered query logic.
middleBrick includes an Input Validation check among its 12 parallel security tests. When you submit a Firestore endpoint URL (or any HTTP API that proxies to Firestore), middleBrick:
- Sends a series of payloads that attempt to inject operators, field paths, and malicious values into typical query parameters.
- Monitors responses for changes in status code, returned data volume, or error messages that suggest the query was modified.
- Reports the finding with severity, the affected parameter, and a short remediation note.
Example CLI usage:
npx middlebrick scan https://api.example.com/posts
The output will list each checked category; if Input Validation fails, you will see a finding similar to:
Finding: Potential NoSQL Injection (Firestore)
Severity: High
Parameter: op
Description: User‑controlled comparison operator passed to Firestore where clause.
Remediation: Validate operator against a whitelist of allowed values (e.g., ['==', '<=', '>=']).
Because middleBrick performs unauthenticated, black‑box scanning, it does not require agents, credentials, or configuration. The scan completes in 5–15 seconds and returns a risk score (A–F) with a per‑category breakdown, making it easy to spot Input Validation issues early in development or in production monitoring.
Firestore-Specific Remediation
The most reliable defense is to treat all user input as untrusted and validate it before it reaches Firestore query builders. Use whitelists for operators and field names, and prefer strongly‑typed parameters for values. Additionally, enforce least‑privilege access through Firestore security rules so that even if a query is tampered with, the data returned is limited to what the authenticated user should see.
Remediated version of the earlier vulnerable function:
const { getFirestore, collection, query, where } = require('firebase-admin/firestore');
const ALLOWED_OPS = ['==', '<=', '>=', 'array-contains', 'in'];
async function getPostsByAuthorSafe(req, res) {
const db = getFirestore();
const { authorId, op } = req.query;
// Validate operator
if (!ALLOWED_OPS.includes(op)) {
return res.status(400).send('Invalid operator');
}
// Validate authorId is a non‑empty string (adjust as needed)
if (typeof authorId !== 'string' || authorId.trim() === '') {
return res.status(400).send('Invalid authorId');
}
const q = query(collection(db, 'posts'), where('authorId', op, authorId.trim()));
const snapshot = await getDocs(q);
res.json(snapshot.docs.map(doc => doc.data()));
}
For field names that must be dynamic (e.g., sorting by a user‑chosen column), validate against a whitelist of permitted fields:
const ALLOWED_FIELDS = ['createdAt', 'title', 'views'];
if (!ALLOWED_FIELDS.includes(sortField)) {
return res.status(400).send('Invalid sort field');
}
const q = query(collection(db, 'posts'), orderBy(sortField));
Firestore security rules provide a second line of defense. A rule set that ties data access to the authenticated user’s UID prevents an attacker from reading another user’s posts, even if they manage to manipulate the query:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /posts/{postId} {
allow read: if request.auth != null && request.auth.uid == resource.data.authorId;
allow create: if request.auth != null;
allow update, delete: if false; // example restriction
}
}
}
By combining input validation, whitelisting, and restrictive security rules, you eliminate the injection vector and ensure that any attempted NoSQL injection is either blocked by the application layer or limited by the rule layer.