Mass Assignment in Django with Bearer Tokens
Mass Assignment in Django with Bearer Tokens — how this specific combination creates or exposes the vulnerability
Mass Assignment occurs when a Django API binds incoming request data directly to a model or serializer without explicit field allowlisting. When endpoints accept Bearer Tokens for authentication but still rely on permissive data binding (for example, using a model’s save() or a serializer’s .create() with request.data), attackers can inject fields they should not control, such as is_staff, role, or permissions. This is a BOLA/IDOR and BFLA/Privilege Escalation risk: even though authentication is present (Bearer Token), authorization is weak because the server implicitly trusts client-supplied keys.
Consider a typical pattern that appears vulnerable:
class Task(models.Model):
title = models.CharField(max_length=200)
completed = models.BooleanField(default=False)
is_internal = models.BooleanField(default=False) # privileged field
owner = models.ForeignKey(User, on_delete=models.CASCADE)
An endpoint that does not restrict fields can be abused:
def update_task(request, task_id):
task = Task.objects.get(id=task_id)
# Unsafe: mass assignment with Bearer Token authentication
for key, value in request.data.items():
setattr(task, key, value)
task.save()
return JsonResponse({'status': 'ok'})
Even when the client presents a valid Bearer Token, the server allows the client to set is_internal or change the owner by including those keys in JSON. This maps to OWASP API Top 10:01 (Broken Object Level Authorization) and 05 (Mass Assignment). In a black-box scan, middleBrick tests such scenarios by submitting payloads that attempt to escalate privileges via unchecked fields, and it flags these as high-severity findings.
Another common exposure is using Django REST Framework’s ModelSerializer with .create() or .update() while passing request.data without fields restriction:
class TaskSerializer(serializers.ModelSerializer):
class Meta:
model = Task
fields = '__all__' # risky: includes privileged fields
If the API relies on Bearer Tokens for auth but does not enforce field-level authorization, the broad fields = '__all__' enables privilege escalation. middleBrick’s checks for Property Authorization and BFLA will surface this by probing endpoints with role-modifying payloads and observing whether unauthorized changes persist.
Bearer Tokens-Specific Remediation in Django — concrete code fixes
Remediation focuses on explicit field allowlisting and ensuring authorization checks are independent of authentication. Do not bind request.data directly; instead, specify which fields can be updated per role. Combine token-based authentication with view- or method-level permission checks.
1) Use serializer fields or extra_kwargs to restrict writable fields per action:
class TaskSerializer(serializers.ModelSerializer):
class Meta:
model = Task
fields = ['title', 'completed']
read_only_fields = ['is_internal', 'owner']
2) Enforce ownership and privilege checks in the view. Even with a Bearer Token, ensure the requesting user can only modify their own non-sensitive fields:
from rest_framework.permissions import BasePermission
class IsOwnerOrReadOnly(BasePermission):
def has_object_permission(self, request, view, obj):
if request.method in ('GET', 'OPTIONS'):
return True
return obj.owner == request.user
def update_task_secure(request, task_id):
task = Task.objects.get(id=task_id)
# Permission: Bearer Token identifies request.user; scope limits to safe fields
if not IsOwnerOrReadOnly().has_object_permission(request, None, task):
return JsonResponse({'error': "forbidden"}, status=403)
# Safe: only allow known-safe fields
safe_data = {k: v for k, v in request.data.items() if k in {'title', 'completed'}}
for key, value in safe_data.items():
setattr(task, key, value)
task.save()
return JsonResponse({'status': 'ok'})
3) For token-based APIs, validate tokens early (e.g., via DRF’s authentication classes) and bind claims to the request user, then apply per-field authorization in serializers:
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
class BearerTokenAuthentication(BaseAuthentication):
def authenticate(self, request):
auth = request.headers.get('Authorization')
if not auth or not auth.startswith('Bearer '):
return None
token = auth.split(' ')[1]
# Validate token and fetch user (pseudocode)
user = validate_bearer_token(token) # implement your validation
if not user:
raise AuthenticationFailed('Invalid token')
return (user, None)
4) In DRF views, prefer .update() with explicit fields and avoid setattr loops over raw request.data. Use partial updates safely:
class TaskViewSet(viewsets.ModelViewSet):
queryset = Task.objects.all()
serializer_class = TaskSerializer
authentication_classes = [BearerTokenAuthentication]
permission_classes = [IsOwnerOrReadOnly]
def update(self, request, *args, **kwargs):
# Only fields defined in TaskSerializer are honored
return super().update(request, *args, **kwargs)
These steps ensure that Bearer Token authentication does not inadvertently enable mass assignment: authentication confirms identity, while explicit field control and ownership checks enforce authorization.
Related CWEs: propertyAuthorization
| CWE ID | Name | Severity |
|---|---|---|
| CWE-915 | Mass Assignment | HIGH |