Sql Injection in Firestore
How Sql Injection Manifests in Firestore
Firestore Sql Injection exploits occur when untrusted user input is incorporated into Firestore queries without proper sanitization. Unlike traditional SQL databases, Firestore uses NoSQL document queries, but injection vulnerabilities still exist through query parameter manipulation.
The most common Firestore injection pattern involves constructing queries using string concatenation or interpolation. For example:
const collection = firestore.collection('users');
const query = collection.where('email', '==', userInput);
When userInput contains special characters or malicious operators, attackers can manipulate query logic. Firestore supports operators like '<', '>', '>=', '<=', 'array-contains', and 'in', which can be exploited if user input isn't validated.
Consider this vulnerable pattern:
const field = req.query.field; // User-controlled
const value = req.query.value; // User-controlled
const operator = req.query.operator || '==';
const query = firestore.collection('products')
.where(field, operator, value);
An attacker could supply field=admin%20or%201%3D1 to potentially bypass authorization checks, though Firestore's query engine prevents some classic SQL injection patterns due to its document model.
More sophisticated attacks target Firestore's array operations:
// Vulnerable: array-contains injection
const tags = req.body.tags; // ['admin', 'user']
const query = firestore.collection('users')
.where('tags', 'array-contains', tags);
If tags is an array, Firestore will match documents where any element matches, potentially expanding the result set beyond intended limits.
Firestore also has unique injection vectors through document path traversal:
// Vulnerable: path injection
const userId = req.params.userId;
const docRef = firestore.doc(`users/${userId}/settings/profile`);
Malicious userId values like admin/../public could access unintended document paths if not properly validated.
Firestore-Specific Detection
Detecting Firestore injection requires both static code analysis and dynamic runtime testing. middleBrick's Firestore-specific scanning examines your API endpoints for vulnerable query construction patterns.
Key detection patterns include:
- Dynamic field access: Code that uses user input to determine collection names, field names, or operators
- Unvalidated array operations: Queries using
array-contains,array-contains-any, orinwith user-controlled arrays - Path traversal: Document references constructed from unvalidated user input
- Wildcard queries: Use of
is-nan,is-null, or!=with dynamic values
middleBrick tests these patterns by sending crafted payloads to your Firestore-backed API endpoints:
POST /api/search HTTP/1.1
Content-Type: application/json
{
"field": "role",
"operator": "==",
"value": "admin" // Test for privilege escalation
}
The scanner also checks for Firestore-specific anti-patterns like:
// Dangerous: dynamic collection names
const collectionName = req.query.collection;
const query = firestore.collection(collectionName)
.where('status', '==', 'active');
middleBrick's detection engine maps findings to OWASP API Security Top 10 categories, specifically A1: Broken Object Level Authorization and A3: Broken Object Property Level Authorization.
For comprehensive testing, middleBrick provides a security risk score (0-100) with detailed findings including:
- Query construction patterns found
- Input validation gaps identified
- Authorization bypass attempts detected
- Compliance mapping to standards like PCI-DSS and SOC2
Firestore-Specific Remediation
Secure Firestore implementations use strict input validation and avoid dynamic query construction. Here are Firestore-specific remediation patterns:
1. Whitelist field names and operators:
const VALID_FIELDS = ['email', 'status', 'createdAt'];
const VALID_OPERATORS = ['==', '>', '<', '>=', '<='];
function createSafeQuery(field, operator, value) {
if (!VALID_FIELDS.includes(field)) {
throw new Error('Invalid field');
}
if (!VALID_OPERATORS.includes(operator)) {
throw new Error('Invalid operator');
}
return firestore.collection('users')
.where(field, operator, value);
}
2. Use parameterized queries for array operations:
// Safe: explicit array validation
function searchByTags(userId, tags) {
if (!Array.isArray(tags) || tags.length === 0) {
throw new Error('Invalid tags');
}
// Validate each tag
const validTags = tags.filter(tag => typeof tag === 'string' && tag.length <= 50);
if (validTags.length !== tags.length) {
throw new Error('Invalid tag format');
}
return firestore.collection('users')
.where('userId', '==', userId)
.where('tags', 'array-contains-any', validTags);
}
3. Secure document path construction:
function getValidUserId(userId) {
// Validate UUID format or other expected pattern
const uuidRegex = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$/;
if (!uuidRegex.test(userId)) {
throw new Error('Invalid user ID format');
}
return userId;
}
// Safe path construction
const userId = getValidUserId(req.params.userId);
const docRef = firestore.doc(`users/${userId}/settings/profile`);
4. Implement query depth limits:
function safeCollectionQuery(collectionName, filters) {
const MAX_DEPTH = 3;
const parts = collectionName.split('/');
if (parts.length > MAX_DEPTH) {
throw new Error('Query depth exceeds maximum');
}
// Only allow specific collections
const allowedCollections = ['users', 'products', 'orders'];
if (!allowedCollections.includes(parts[0])) {
throw new Error('Invalid collection');
}
// Build query with validated filters
let query = firestore.collection(collectionName);
Object.entries(filters).forEach(([field, value]) => {
if (VALID_FIELDS.includes(field)) {
query = query.where(field, '==', value);
}
});
return query;
}
5. Use Firestore security rules as defense-in-depth:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /users/{userId} {
allow read, write: if request.auth.uid == userId;
match /settings/{setting} {
allow read, write: if request.auth.uid == userId;
}
}
match /public/{document=**} {
allow read: if true;
allow write: if false;
}
}
}
For continuous security, middleBrick's Pro plan includes automated scanning that can be integrated into your CI/CD pipeline. Set up GitHub Actions to scan your Firestore-backed APIs before deployment:
name: Security Scan
on: [pull_request]
jobs:
security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Run middleBrick Scan
run: |
npx middlebrick scan https://api.yourdomain.com
env:
MIDDLEBRICK_API_KEY: ${{ secrets.MIDDLEBRICK_API_KEY }}
- name: Fail on High Risk
run: |
# Check if scan found critical issues
if [ $(grep -c "CRITICAL" security-report.json) -gt 0 ]; then
exit 1
fi
Related CWEs: inputValidation
| CWE ID | Name | Severity |
|---|---|---|
| CWE-20 | Improper Input Validation | HIGH |
| CWE-22 | Path Traversal | HIGH |
| CWE-74 | Injection | CRITICAL |
| CWE-77 | Command Injection | CRITICAL |
| CWE-78 | OS Command Injection | CRITICAL |
| CWE-79 | Cross-site Scripting (XSS) | HIGH |
| CWE-89 | SQL Injection | CRITICAL |
| CWE-90 | LDAP Injection | HIGH |
| CWE-91 | XML Injection | HIGH |
| CWE-94 | Code Injection | CRITICAL |