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 ID | Name | Severity |
|---|---|---|
| CWE-250 | Execution with Unnecessary Privileges | HIGH |
| CWE-639 | Insecure Direct Object Reference | CRITICAL |
| CWE-732 | Incorrect Permission Assignment | HIGH |