HIGH bola idordjangobasic auth

Bola Idor in Django with Basic Auth

Bola Idor in Django with Basic Auth — how this specific combination creates or exposes the vulnerability

Broken Object Level Authorization (BOLA), often referred to as Insecure Direct Object References (IDOR), occurs when an API exposes a reference to an object without verifying that the authenticated actor has permission to access that specific object. In Django, this commonly surfaces when a view identifies an object using a user-supplied integer or UUID (e.g., /api/invoices/42) and fails to confirm the requesting user owns or is allowed to view that invoice. When Basic Auth is used without additional authorization checks, the problem is exacerbated because authentication and authorization are weakly coupled.

With Basic Auth in Django, credentials are sent with every request and typically validated via a session or token-like mechanism (e.g., using Django’s built-in authentication with HTTP Basic). The framework logs the user in by setting a user ID in the session, but if the developer only checks that a user is authenticated (via @login_required or request.user.is_authenticated) and does not enforce object-level permissions, BOLA becomes straightforward to exploit. An attacker can enumerate predictable identifiers and access other users’ resources simply by changing the object ID in the URL, because the backend does not compare the requested object’s ownership against the authenticated user’s identity.

Consider a Django view that retrieves a user’s profile using a numeric ID without verifying that the profile belongs to the requesting user:

from django.contrib.auth.decorators import login_required
from django.http import JsonResponse
from .models import UserProfile

@login_required
def get_profile(request, profile_id):
    profile = UserProfile.objects.get(id=profile_id)
    return JsonResponse({'display_name': profile.display_name, 'email': profile.email})

In this example, the @login_required decorator ensures the user is authenticated via Basic Auth (or any other mechanism), but it does not ensure the profile_id belongs to request.user. An authenticated attacker can iterate through profile IDs and read other users’ sensitive data, leading to a BOLA violation. The same risk applies when using Django REST Framework with Basic Auth headers, where authentication is handled but view-level permissions may be missing or misconfigured.

Django’s authorization layer does not automatically enforce object ownership. You must explicitly scope queries to the requesting user. For instance, UserProfile.objects.filter(user=request.user, id=profile_id) mitigates the risk by ensuring the object is both existing and owned by the user. Relying solely on authentication without this ownership check is what makes the combination of Django, Basic Auth, and predictable object references dangerous.

Basic Auth-Specific Remediation in Django — concrete code fixes

To remediate BOLA when using Basic Auth in Django, always couple authentication with explicit object ownership checks and use least-privilege queries. Below are concrete, safe patterns with working code examples.

1. Use filter with request.user instead of get

Replace direct object retrieval with a filter that includes the user as a condition. This ensures the object both exists and belongs to the requester.

from django.contrib.auth.decorators import login_required
from django.http import JsonResponse, Http404
from .models import UserProfile

@login_required
def get_profile_safe(request, profile_id):
    profile = UserProfile.objects.filter(user=request.user, id=profile_id).first()
    if profile is None:
        raise Http404('Profile not found or access denied')
    return JsonResponse({'display_name': profile.display_name, 'email': profile.email})

2. Use Django REST Framework with permission classes

If using DRF, combine IsAuthenticated with a custom object-level permission to enforce ownership.

from rest_framework.permissions import BasePermission, IsAuthenticated
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from .models import UserProfile

class IsOwnerOrReadOnly(BasePermission):
    def has_object_permission(self, request, view, obj):
        # Read permissions are allowed for any request,
        # but write/delete require ownership
        if request.method in ('GET',):
            return True
        return obj.user == request.user

class ProfileDetail(APIView):
    permission_classes = [IsAuthenticated, IsOwnerOrReadOnly]

    def get(self, request, profile_id):
        profile = UserProfile.objects.filter(user=request.user, id=profile_id).first()
        if profile is None:
            return Response({'detail': 'Not found.'}, status=status.HTTP_404_NOT_FOUND)
        return Response({'display_name': profile.display_name, 'email': profile.email})

3. Scoped querysets in class-based views

In generic views, override get_queryset to restrict results to the requesting user.

from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic import DetailView
from .models import UserProfile

class ProfileDetailView(LoginRequiredMixin, DetailView):
    model = UserProfile
    pk_url_kwarg = 'profile_id'
    context_object_name = 'profile'

    def get_queryset(self):
        return UserProfile.objects.filter(user=self.request.user)

4. Avoid exposing internal IDs when possible

Use UUIDs or slugs that do not leak sequential information, and continue to enforce ownership on each request. Even with non-predictable identifiers, missing ownership checks remain a BOLA risk.

5. Validate and normalize inputs

Ensure profile_id is properly validated and coerced to the expected type to avoid type confusion or injection-related bypasses.

from django.core.exceptions import ValidationError
def validate_positive_int(value):
    try:
        ivalue = int(value)
        if ivalue <= 0:
            raise ValidationError('Invalid ID')
        return ivalue
    except (ValueError, TypeError):
        raise ValidationError('Invalid ID')

Apply such validation before using the parameter in queries.

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

Does using Basic Auth with HTTPS fully prevent BOLA in Django?
No. HTTPS protects credentials in transit, but it does not prevent BOLA. Authorization checks (object ownership) are still required; otherwise, authenticated users can access other users’ objects via predictable IDs.
How can I test for BOLA in Django views that use Basic Auth?
Use an authenticated request with a known profile_id belonging to another user and verify the response is 404 or access denied. Automated scanners can also probe sequential or guessed IDs to detect missing ownership checks.