HIGH mass assignmentdjangohmac signatures

Mass Assignment in Django with Hmac Signatures

Mass Assignment in Django with Hmac Signatures — how this specific combination creates or exposes the vulnerability

Mass Assignment in Django occurs when a model is populated directly from user-supplied data, such as a request query or body, without explicitly restricting which fields can be set. This becomes especially risky when Hmac Signatures are used to bind a request to a particular operation but the application fails to validate or scope the signed payload against the model’s allowed fields.

Consider a Django model UserProfile with fields user, bio, and is_premium. If a view uses an Hmac Signature to verify that a request originated from a trusted source, but then passes the entire parsed payload to update(**data) or a model form, an attacker who can influence the signed data (e.g., by replaying or manipulating non-signature portions of the request) may set is_premium to True. The signature may validate correctly if the attacker knows or can forge the key, or if the signature covers only a subset of the payload while the rest is taken from user-controlled sources.

In a typical flow, the server generates an Hmac for a canonical representation of an action (e.g., user_id:action:resource_id) and includes it in a query parameter or header. The view then verifies the Hmac and proceeds to update the model using unchecked data from the request. Because mass assignment does not differentiate between safe and dangerous fields, sensitive attributes can be altered if they are included in the incoming data and not explicitly excluded.

This combination exposes two distinct attack surfaces: (1) the Hmac may not cover all fields that are bound to the model, and (2) even when the Hmac covers a token or identifier, the application may still trust other request parameters that were not integrity-protected. For example, an attacker could issue a legitimate, signed request to update a profile bio, then replay the signature while modifying the is_premium field in the POST body, relying on mass assignment to apply the unauthorized change.

Django’s form and serializer layers provide mechanisms to declare allowed fields, but if developers bypass these protections by manually constructing model instances or using QueryDict unpacking, the risk persists. The security issue is not the Hmac algorithm itself, but the lack of strict field allow-listing and the assumption that a verified signature implies safe data.

Hmac Signatures-Specific Remediation in Django — concrete code fixes

To mitigate mass assignment when using Hmac Signatures in Django, explicitly define which fields are permitted for each operation and apply them after successful signature verification. Never bind model updates directly to request.POST or request body without filtering by a declared allow-list.

Below are concrete, working examples that show how to combine Hmac verification with strict field handling.

Example 1: Hmac verification with an explicit field allow-list using dictionary comprehension.

import hmac
import hashlib
from django.http import JsonResponse
from django.views import View

class UpdateProfileView(View):
    ALLOWED_FIELDS = {'bio', 'display_name', 'theme'}
    SECRET_KEY = b'your-secret-key'  # store securely, e.g., settings.SECRET_KEY

    def post(self, request, user_id):
        # Expecting: signature in header X-Signature, payload in request.body JSON
        signature = request.META.get('HTTP_X_SIGNATURE')
        payload = request.POST  # or request.body parsed to dict

        # Verify Hmac over canonical string; here we sign a sorted key=value concatenation
        message = '&'.join(f'{k}={payload[k]}' for k in sorted(payload) if k != 'signature')
        expected = hmac.new(self.SECRET_KEY, message.encode(), hashlib.sha256).hexdigest()
        if not hmac.compare_digest(expected, signature):
            return JsonResponse({'error': 'invalid signature'}, status=403)

        # Apply strict allow-list to prevent mass assignment
        update_data = {k: payload[k] for k in payload if k in self.ALLOWED_FIELDS}

        profile = UserProfile.objects.get(user_id=user_id)
        for key, value in update_data.items():
            setattr(profile, key, value)
        profile.save()

        return JsonResponse({'status': 'ok'})

Example 2: Using Django REST Framework serializers with Hmac verification on the viewset.

import hmac
import hashlib
from rest_framework import viewsets, status
from rest_framework.response import Response
from .models import UserSettings
from .serializers import UserSettingsSerializer

class HmacProtectedViewSet(viewsets.ModelViewSet):
    queryset = UserSettings.objects.all()
    serializer_class = UserSettingsSerializer
    ALLOWED_FIELDS = {'theme', 'notifications_enabled'}
    SECRET_KEY = b'your-secret-key'

    def perform_create(self, serializer):
        # Not used here, shown for completeness
        pass

    def perform_update(self, serializer):
        # Verify Hmac before allowing update
        signature = self.request.META.get('HTTP_X_SIGNATURE')
        payload = self.request.data
        message = '&'.join(f'{k}={payload[k]}' for k in sorted(payload) if k != 'signature')
        expected = hmac.new(self.SECRET_KEY, message.encode(), hashlib.sha256).hexdigest()
        if not hmac.compare_digest(expected, signature):
            raise serializers.ValidationError('invalid signature')

        # Apply allow-list explicitly
        filtered = {k: v for k, v in payload.items() if k in self.ALLOWED_FIELDS}
        serializer.save(**filtered)

Example 3: Scoped Hmac that binds to specific action and resource, reducing replay risk.

import hmac
import hashlib
import time
from django.http import JsonResponse

SCOPE = 'profile:update'
def build_signature(user_id, action, timestamp, extra='', secret=b'secret'):
    canonical = f'{SCOPE}:{user_id}:{action}:{timestamp}:{extra}'
    return hmac.new(secret, canonical.encode(), hashlib.sha256).hexdigest()

class ScopedUpdateView:
    def handle(self, request, user_id):
        timestamp = request.GET.get('ts')
        signature = request.GET.get('sig')
        if abs(time.time() - int(timestamp)) > 300:
            return JsonResponse({'error': 'stale request'}, status=400)

        expected = build_signature(user_id, 'update', timestamp, extra='', secret=self.SECRET_KEY)
        if not hmac.compare_digest(expected, signature):
            return JsonResponse({'error': 'invalid signature'}, status=403)

        # Use only explicitly allowed data, never raw request.data for model updates
        profile = UserProfile.objects.get(user_id=user_id)
        profile.bio = request.GET.get('bio', profile.bio)  # explicit field mapping
        profile.is_premium = False  # never set from unsigned/user input
        profile.save()
        return JsonResponse({'status': 'ok'})

Related CWEs: propertyAuthorization

CWE IDNameSeverity
CWE-915Mass Assignment HIGH

Frequently Asked Questions

Why does using an Hmac Signature not automatically protect against mass assignment in Django?
An Hmac Signature verifies integrity and origin of data, but it does not restrict which fields can be assigned to a model. If the view applies all request data to the model, mass assignment can still modify sensitive fields. You must explicitly allow-list fields after signature verification.
How can I safely map signed payload fields to a Django model without risking mass assignment?
After verifying the Hmac, construct a dictionary that includes only the fields you expect and that you have declared as allowed. Use this filtered dictionary when creating or updating model instances, avoiding direct unpacking of raw request data.