HIGH insecure direct object referencedjangomongodb

Insecure Direct Object Reference in Django with Mongodb

Insecure Direct Object Reference in Django with Mongodb — how this specific combination creates or exposes the vulnerability

Insecure Direct Object Reference (IDOR) occurs when an API exposes a reference to an internal object—such as a database record—and allows an authenticated but unauthorized actor to access or modify that object. In a Django application using MongoDB, IDOR commonly arises when URL or query parameters directly map to MongoDB document identifiers (e.g., ObjectId or custom keys) without verifying that the requesting user has the necessary permissions on that specific document.

Consider a Django view that retrieves a user profile using a profile_id from the URL and a MongoDB collection profiles:

from django.http import JsonResponse
from pymongo import MongoClient

def get_profile(request, profile_id):
    client = MongoClient('mongodb://localhost:27017/')
    db = client['mydb']
    profile = db.profiles.find_one({'_id': profile_id})
    if profile is None:
        return JsonResponse({'error': 'Not found'}, status=404)
    return JsonResponse({'profile': profile})

If the view trusts the profile_id supplied by the caller and does not check that the authenticated user is allowed to view that profile, an attacker can iterate through valid ObjectIds and read other users’ profiles. This is an IDOR: the object reference (profile_id) is directly exposed and not bound to an access control check.

Django’s own permissions and object-level permissions (e.g., via Django Guardian) are not automatically applied to MongoDB documents. Because MongoDB is typically queried directly (e.g., using pymongo), the developer must enforce ownership or role checks manually. Attack patterns include enumerating valid IDs in sequential or predictable ways, accessing another tenant’s data in a multi-tenant setup, or modifying resources by guessing IDs in PUT/PATCH/DELETE endpoints.

In a microservice or API gateway context, an unauthenticated or low-privilege service account used by Django to connect to MongoDB may expose more data than intended if field-level permissions are not encoded in the query. For example, a query that returns full documents without filtering sensitive fields can lead to Data Exposure even when IDOR is not present. IDOR in this stack is therefore a combination of predictable object references, missing ownership validation, and excessive data exposure in query results.

Mongodb-Specific Remediation in Django — concrete code fixes

To prevent IDOR when using Django with MongoDB, enforce per-request ownership or role-based checks before returning or modifying a document. Store the mapping between Django user identities and MongoDB document ownership explicitly (e.g., a user_id field in each document) and include that field in every query.

Below is a secure pattern that binds the query to the authenticated user’s ID, using PyMongo safely within a Django view:

from django.http import JsonResponse, HttpResponseForbidden
from django.contrib.auth.decorators import login_required
from pymongo import MongoClient

@login_required
def get_profile_secure(request, profile_id):
    client = MongoClient('mongodb://localhost:27017/')
    db = client['mydb']
    # Ensure the profile belongs to the requesting user
    profile = db.profiles.find_one({'_id': profile_id, 'user_id': request.user.id})
    if profile is None:
        return HttpResponseForbidden({'error': 'Access denied or not found'})
    # Explicitly exclude sensitive fields that should not be returned
    profile.pop('password_hash', None)
    profile.pop('tokens', None)
    return JsonResponse({'profile': profile})

For updates, use an update operation that includes the ownership filter to ensure the document both exists and is owned by the user:

@login_required
def update_profile(request, profile_id):
    client = MongoClient('mongodb://localhost:27017/')
    db = client['mydb']
    update_data = {'$set': request.body_as_json}
    result = db.profiles.update_one(
        {'_id': profile_id, 'user_id': request.user.id},
        update_data
    )
    if result.matched_count == 0:
        return HttpResponseForbidden({'error': 'Update not permitted or document not found'})
    return JsonResponse({'status': 'updated'})

When using ObjectIds, ensure they are validated before inclusion in queries to avoid injection-like parsing errors. You can also centralize access checks in a service layer or repository function to avoid repeating checks across views:

def get_document_or_404(collection, doc_id, user_id):
    doc = collection.find_one({'_id': doc_id, 'user_id': user_id})
    if doc is None:
        raise Http404('Document not found or access denied')
    return doc

In multi-tenant scenarios, include a tenant_id field in both the user model and documents, and filter by tenant_id in every query. For read-heavy endpoints, consider projection to limit returned fields and reduce Data Exposure, and validate that indexes support the ownership filter for performance.

Using the middleBrick CLI, you can scan endpoints like this to detect IDOR and related issues:

middlebrick scan https://api.example.com/profiles/<profile_id>

Within the dashboard, you can track these findings over time; with the Pro plan you can enable continuous monitoring so that any regression in permissions is flagged early. The GitHub Action can fail a build if a scan detects IDOR patterns, helping prevent insecure patterns from reaching production.

Related CWEs: bolaAuthorization

CWE IDNameSeverity
CWE-250Execution with Unnecessary Privileges HIGH
CWE-639Insecure Direct Object Reference CRITICAL
CWE-732Incorrect Permission Assignment HIGH

Frequently Asked Questions

Can IDOR happen even when using MongoDB's ObjectId type for document identifiers?
Yes. ObjectId values are predictable and enumerable if an attacker can guess valid IDs. Without an ownership check (e.g., matching a user_id field), exposing endpoints that accept an ObjectId as a parameter can lead to IDOR.
Does using MongoDB’s RBAC or field-level encryption remove the need for Django-level access checks?
No. MongoDB RBAC protects database-level operations, not application-level authorization. Field-level encryption protects data at rest and in transit but does not prevent an authenticated user from requesting another user’s document. Always enforce ownership or role checks in your Django views.