HIGH command injectionfirestore

Command Injection in Firestore

How Command Injection Manifests in Firestore

Command injection in Firestore contexts typically occurs when user input is improperly incorporated into database queries or security rules without proper sanitization. Unlike traditional command injection in operating systems, Firestore injection attacks exploit the database's query language and security model.

The most common pattern involves dynamic query construction where user input directly influences Firestore's query structure. For example, consider an application that allows users to search for documents by field:

// Vulnerable code - direct string interpolation
const field = req.query.field;
const value = req.query.value;
const query = db.collection('users').where(field, '==', value);
const results = await query.get();

This code is vulnerable because an attacker can manipulate the field parameter to inject arbitrary query conditions. An attacker could supply field=role and value=admin to retrieve administrative users, or craft more complex queries that bypass intended security constraints.

Another manifestation occurs in Firestore security rules where dynamic evaluation happens. Consider these vulnerable rules:

match /users/{userId} {
  allow read: if request.auth.uid == userId;
  allow write: if request.auth.uid == userId;
}

While this looks secure, if the application layer constructs queries using user input for the userId parameter, an attacker could potentially enumerate users or access unauthorized documents by manipulating the path parameter.

Firestore's array operations can also be exploited. A vulnerable pattern might look like:

// Vulnerable - no input validation
const updateData = {
  [req.body.field]: admin.firestore.FieldValue.arrayUnion(req.body.value)
};
await db.collection('users').doc(userId).update(updateData);

Here, an attacker could inject malicious field names or values that manipulate the array operations in unintended ways.

Batch operations present another injection vector. Consider:

const batch = db.batch();
req.body.updates.forEach(update => {
  batch.update(db.collection('users').doc(update.id), update.changes);
});
await batch.commit();

If update.id or update.changes contains malicious data, an attacker could modify documents they shouldn't have access to.

Firestore-Specific Detection

Detecting command injection in Firestore requires examining both the application code and the runtime behavior of queries. Here are the specific techniques and tools for identifying these vulnerabilities:

Static Code Analysis should look for patterns where user input directly influences Firestore operations. Key indicators include:

  • Dynamic field names constructed from request parameters
  • Unvalidated path parameters used in document references
  • Array operations with user-controlled field names
  • Batch operations processing unvalidated input
  • Security rules that rely on application-layer validation

Runtime Detection involves monitoring query patterns and access attempts. Look for:

  • Queries with unexpected field names or operators
  • Access attempts to system collections (like _firestore)
  • Unusual array operations or field manipulations
  • Batch operations affecting multiple documents outside normal patterns

middleBrick Scanning specifically tests for Firestore command injection vulnerabilities through black-box scanning. The tool automatically:

  • Tests for path traversal in document references
  • Attempts to inject malicious field names and operators
  • Scans for improper array operation handling
  • Checks batch operation security
  • Analyzes security rule effectiveness against injection attempts

The scanner returns a security score (A-F) with specific findings about Firestore injection vulnerabilities, prioritized by severity with remediation guidance.

Monitoring and Logging should track:

// Enhanced logging for detection
const logQuery = (query, userId) => {
  console.log(`Query by ${userId}: ${query._query.toString()}`);
  // Log suspicious patterns
  if (query._query._fields.some(f => f.includes('$') || f.includes('..'))) {
    console.warn('Suspicious query detected:', query._query._fields);
  }
};

Production monitoring should alert on queries with unusual field names, unexpected operators, or access patterns that deviate from normal user behavior.

Firestore-Specific Remediation

Remediating command injection in Firestore requires a defense-in-depth approach with input validation, secure query construction, and proper security rules. Here are the specific techniques:

Input Validation and Whitelisting is the first line of defense. Never trust user input for field names or operators:

const VALID_FIELDS = ['name', 'email', 'role', 'status'];
const VALID_OPERATORS = ['==', '>', '<', '>=', '<='];

function validateQuery(field, operator, value) {
  if (!VALID_FIELDS.includes(field)) {
    throw new Error('Invalid field');
  }
  if (!VALID_OPERATORS.includes(operator)) {
    throw new Error('Invalid operator');
  }
  // Additional validation based on field type
  if (field === 'email' && !isValidEmail(value)) {
    throw new Error('Invalid email format');
  }
  return { field, operator, value };
}

// Secure query construction
const { field, operator, value } = validateQuery(
  req.query.field, 
  req.query.operator, 
  req.query.value
);
const query = db.collection('users').where(field, operator, value);
const results = await query.get();

Parameterized Queries prevent injection by separating query structure from data:

// Always use parameterized queries
async function getUserByEmail(email) {
  if (!isValidEmail(email)) {
    throw new Error('Invalid email format');
  }
  const snapshot = await db.collection('users')
    .where('email', '==', email)
    .limit(1)
    .get();
  return snapshot.docs[0];
}

// Never construct queries with string concatenation
// Vulnerable - DO NOT USE
const unsafeQuery = `
  SELECT * FROM users 
  WHERE ${field} ${operator} "${value}"
`;

Secure Security Rules add a second layer of defense:

match /users/{userId} {
  allow read: if 
    request.auth != null &&
    isValidUserId(userId) &&
    (request.auth.uid == userId || 
     getRole(request.auth.uid) == 'admin');
  
  allow write: if 
    request.auth != null &&
    isValidUserId(userId) &&
    request.auth.uid == userId;
}

function isValidUserId(userId) {
  return userId.matches('^user_[a-zA-Z0-9_-]{10,20}$');
}

function getRole(userId) {
  return get(/databases/$(database)/documents/users/$(userId)).data.role;
}

Array Operation Security requires validating field names and values:

const VALID_ARRAY_FIELDS = ['roles', 'permissions', 'tags'];

async function safeArrayUpdate(docId, field, value) {
  if (!VALID_ARRAY_FIELDS.includes(field)) {
    throw new Error('Invalid array field');
  }
  
  // Validate value type
  if (field === 'roles' && !isValidRole(value)) {
    throw new Error('Invalid role');
  }
  
  const docRef = db.collection('users').doc(docId);
  await docRef.update({
    [field]: admin.firestore.FieldValue.arrayUnion(value)
  });
}

async function safeArrayRemove(docId, field, value) {
  if (!VALID_ARRAY_FIELDS.includes(field)) {
    throw new Error('Invalid array field');
  }
  
  const docRef = db.collection('users').doc(docId);
  await docRef.update({
    [field]: admin.firestore.FieldValue.arrayRemove(value)
  });
}

Batch Operation Security requires validating all inputs before execution:

async function secureBatchUpdate(updates) {
  const batch = db.batch();
  const validUpdates = [];
  
  for (const update of updates) {
    // Validate document ID format
    if (!isValidDocumentId(update.id)) {
      throw new Error(`Invalid document ID: ${update.id}`);
    }
    
    // Validate field names and values
    const docRef = db.collection('users').doc(update.id);
    for (const [field, value] of Object.entries(update.changes)) {
      if (!VALID_FIELDS.includes(field)) {
        throw new Error(`Invalid field: ${field}`);
      }
      // Type validation
      if (field === 'age' && typeof value !== 'number') {
        throw new Error('Age must be a number');
      }
    }
    
    batch.update(docRef, update.changes);
    validUpdates.push(update.id);
  }
  
  await batch.commit();
  return validUpdates;
}

function isValidDocumentId(id) {
  // Firestore document IDs can't contain 
  // forward slashes, backslashes, or certain other characters
  return /^[a-zA-Z0-9_-]{1,100}$/.test(id);
}

Monitoring and Alerting should be implemented to detect injection attempts:

// Enhanced monitoring
const suspiciousPatterns = [
  /\$\w+/,           // Firestore operators
  /\.\./,            // Path traversal
  /\b(or|and|not)\b/i // SQL-like keywords
];

function monitorQuery(query, userId) {
  const queryStr = query._query.toString();
  for (const pattern of suspiciousPatterns) {
    if (pattern.test(queryStr)) {
      console.warn(`Suspicious query detected from ${userId}:`, queryStr);
      // Alert or block the query
      return false;
    }
  }
  return true;
}

Related CWEs: inputValidation

CWE IDNameSeverity
CWE-20Improper Input Validation HIGH
CWE-22Path Traversal HIGH
CWE-74Injection CRITICAL
CWE-77Command Injection CRITICAL
CWE-78OS Command Injection CRITICAL
CWE-79Cross-site Scripting (XSS) HIGH
CWE-89SQL Injection CRITICAL
CWE-90LDAP Injection HIGH
CWE-91XML Injection HIGH
CWE-94Code Injection CRITICAL

Frequently Asked Questions

How does command injection in Firestore differ from traditional SQL injection?
Firestore command injection exploits the document-based query language and security model rather than SQL syntax. Instead of injecting SQL keywords, attackers manipulate field names, operators, and document paths. The attack surface includes dynamic query construction, array operations, and security rule bypass attempts. Firestore's NoSQL nature means injection patterns focus on document references, collection paths, and query parameters rather than table names and SQL syntax.
Can Firestore security rules alone prevent command injection?
No, security rules should be a second line of defense, not the primary protection. While rules can block unauthorized access attempts, they execute after the application layer processes user input. If the application constructs malicious queries before security rules evaluate them, you may still experience data exposure or performance issues. Always validate and sanitize input at the application layer, then use security rules as additional protection.