Injection Flaws in Django with Firestore
Injection Flaws in Django with Firestore — how this specific combination creates or exposes the vulnerability
Injection flaws occur when untrusted data is interpreted as part of a command or query. In Django applications that use Google Cloud Firestore as a backend, risks arise not from SQL (Firestore is a NoSQL document store) but from improper construction of queries, insufficient validation of document paths, and unsafe handling of data used to build queries and responses. When Django forms, URL parameters, or request bodies directly influence Firestore collection names, document IDs, field names, or composite query structures, the application can expose an unauthenticated attack surface that middleBrick tests as part of its Injection check.
Firestore’s query language does not support traditional SQL-style injection, but injection-like behavior can occur through:
- Dynamic collection or document references built from user input without strict allowlisting, enabling NoSQL tampering or path traversal.
- Unsafe use of dictionary expansion (**kwargs) when constructing queries, which can inject unexpected filters or field projections.
- Deserialization and rendering of Firestore documents into templates or LLM prompts without output encoding, leading to stored or reflected injection in downstream AI components.
middleBrick’s black-box scan tests endpoints that accept parameters used to query Firestore, including attempts to manipulate document IDs, collection names, and field values. Findings often include missing input validation, missing type coercion checks, and missing output encoding when documents are rendered in HTML or passed to LLM tools. OWASP API Top 10 categories such as API1:2023 – Broken Object Level Authorization and API5:2023 – Injection map to these issues, and similar risks appear in PCI-DSS and SOC2 control mappings.
An example risk pattern: a view that accepts a user_id parameter and uses it to read a document from a Firestore collection. If the parameter is used to build a document reference without validation, an attacker can attempt path traversal (e.g., users/../../../secrets/config) or reference collections that expose sensitive data. middleBrick’s BOLA/IDOR and Injection checks surface these patterns by probing document paths and observing whether unauthorized data is returned.
Firestore-Specific Remediation in Django — concrete code fixes
Remediation focuses on strict input validation, allowlisting, and safe query construction. Avoid dynamic collection names; if necessary, validate against a strict pattern. Never pass raw user input into dictionary unpacking for queries. Use Firestore client methods explicitly and enforce field-level permissions on reads and writes.
Safe document read with allowlisted IDs
Validate identifiers against a regex pattern and use a hardcoded collection name. This prevents path traversal and ensures only expected document structures are accessed.
import re
from google.cloud import firestore
from django.http import JsonResponse, HttpResponseBadRequest
def get_user_profile(request, user_id):
# Allowlist: alphanumeric and underscores, 3–32 chars
if not re.match(r'^[a-zA-Z0-9_]{3,32}$', user_id):
return HttpResponseBadRequest('Invalid user ID')
db = firestore.Client()
doc_ref = db.collection('users').document(user_id)
doc = doc_ref.get()
if not doc.exists:
return JsonResponse({'error': 'Not found'}, status=404)
return JsonResponse(doc.to_dict())
Safe query construction without dynamic kwargs
Build filters explicitly rather than using **kwargs from user input. For field-based filtering, map allowed fields to Firestore field names and enforce value types.
from google.cloud import firestore
from django.http import JsonResponse
ALLOWED_FILTER_FIELDS = {'status', 'category', 'created_at'}
def list_items(request):
db = firestore.Client()
query = db.collection('items')
for field in ALLOWED_FILTER_FIELDS:
value = request.GET.get(field)
if value:
# Map to known safe values or coerce types as needed
if field == 'status' and value in ('open', 'closed', 'pending'):
query = query.where(field, '==', value)
elif field == 'category' and value.isalpha():
query = query.where(field, '==', value)
docs = query.stream()
results = [{'id': doc.id, **doc.to_dict()} for doc in docs]
return JsonResponse(results, safe=False)
Output encoding when rendering documents in templates or prompts
When documents are rendered into HTML or passed to downstream systems (e.g., LLM prompts), encode or sanitize sensitive or user-controlled fields to prevent injection into those contexts. For LLM tools, validate and constrain outputs to avoid execution of unintended code or leakage of secrets.
from django.utils.html import escape
def render_user_data(user_doc):
data = user_doc.to_dict()
safe_data = {
'username': escape(data.get('username', '')),
'email': escape(data.get('email', ''))
}
return safe_data