Broken Access Control in Django with Mongodb
Broken Access Control in Django with Mongodb — how this specific combination creates or exposes the vulnerability
Broken Access Control (BAC) occurs when an application fails to enforce proper authorization checks, allowing an authenticated user to access or modify resources they should not. When using Django with MongoDB, BAC risks can arise from misaligned permission models and data access patterns. Django’s default authorization relies on relational models and row-level constraints, whereas MongoDB is schema-less and often encourages embedding or flexible document references. If developers map Django user permissions to MongoDB queries using loosely defined filters—such as querying a collection with only user-provided identifiers without validating ownership or role constraints—an attacker can manipulate parameters to access other users’ documents.
For example, consider a view that retrieves user data by appending a user ID from the URL directly into a MongoDB filter: Document.objects.filter(user_id=request.GET.get('user_id')). If the view does not verify that the requesting user matches that ID or lacks a superuser role, a low-privilege user can enumerate or modify other users’ records. This pattern is common in APIs built with Django and MongoDB where the developer assumes the client-supplied ID is trustworthy. Additionally, embedding role or permission flags inside documents without server-side enforcement can lead to privilege escalation if an attacker modifies their embedded role field and the backend does not revalidate against a centralized identity store.
Another BAC vector specific to this stack is improper default rules in MongoDB access control. If the MongoDB instance permits broad read/write access from the application layer and Django’s database user is over-privileged, any vulnerability in the Django app—such as an unvalidated input leading to an injection—can result in unauthorized document operations. Furthermore, inconsistent use of ObjectId versus string identifiers across Django models and raw PyMongo queries can cause logic flaws where a developer believes they are filtering by a valid reference but inadvertently bypass scoping checks due to type mismatch or missing normalization.
Mongodb-Specific Remediation in Django — concrete code fixes
To mitigate Broken Access Control when using Django with MongoDB, enforce strict ownership checks and role validation on every query. Always treat user-supplied identifiers as untrusted and derive document ownership from the authenticated user rather than client input. Below are concrete code examples demonstrating secure patterns.
1. Enforce Ownership with Parameterized Filters
Ensure each query includes the authenticated user’s ID as a mandatory filter condition. Avoid concatenating or interpolating user input directly into the filter.
from django.http import HttpResponseForbidden
from django.contrib.auth.decorators import login_required
from bson.objectid import ObjectId
import pymongo
# Assume db is a connected pymongo database
@login_required
def get_user_document(request, doc_id):
user_id = request.user.id # Django user ID (UUID or int)
doc_id = request.GET.get('id')
if not doc_id:
return HttpResponseForbidden('Missing document ID')
# Secure: scope query by both user_id and document ID
collection = db['user_documents']
document = collection.find_one({
'user_id': user_id,
'_id': ObjectId(doc_id)
})
if document is None:
return HttpResponseForbidden('Document not found or access denied')
return document
2. Role-Based Access Control with Centralized Checks
Do not rely on embedded role flags in documents. Instead, use Django groups or a dedicated permissions service and validate server-side before executing MongoDB operations.
from django.contrib.auth.decorators import user_passes_test
def is_admin(user):
# Use Django’s built-in group/permission system
return user.groups.filter(name='Admins').exists()
@user_passes_test(is_admin)
def admin_only_operation(request):
# Even if the user is an admin, scope by tenant or org if applicable
results = db['sensitive_data'].find({
'org_id': request.user.org_id # enforce org-level scoping
})
return list(results)
3. Normalize Identifiers and Use Safe References
Consistently use ObjectId for MongoDB references and validate types before querying to avoid bypassing access controls due to type confusion.
from bson.errors import InvalidId
def safe_find_by_id(collection, id_string, user_id):
try:
obj_id = ObjectId(id_string)
except InvalidId:
raise ValueError('Invalid ObjectId')
# Ensure the document belongs to the user
return collection.find_one({
'_id': obj_id,
'owner_id': user_id # explicit ownership field
})
4. Principle of Least Privilege for MongoDB Connection
Configure the MongoDB user for the Django application with minimal required permissions. Avoid using a superuser account for routine operations. Use role-based privileges in MongoDB to restrict write access to specific collections and read access to authorized datasets.