Challenge 3: Spot the Mass Assignment Risk — Possible Solution ==================================================================== # BEFORE — vulnerable from django import forms from .models import UserProfile class UserProfileForm(forms.ModelForm): class Meta: model = UserProfile fields = "__all__" # ⚠️ includes is_verified, not just bio/avatar_url # AFTER — safe explicit allowlist class UserProfileForm(forms.ModelForm): class Meta: model = UserProfile fields = ["bio", "avatar_url"] # is_verified deliberately excluded WHAT REAL-WORLD EXPLOIT THE ORIGINAL VERSION WAS VULNERABLE TO ------------------------------------------------------------------ Because HTML form data is just a flat set of key/value pairs submitted by the client, an attacker can add EXTRA fields to a POST request beyond what the rendered
actually displayed — for example, using browser dev tools or a tool like curl/Postman to submit a request that includes "is_verified=true" alongside the legitimate bio and avatar_url fields. With fields = "__all__", Django's ModelForm has no way to distinguish "fields the user is supposed to be able to set" from "every field the model happens to have" — so that extra is_verified=true value gets accepted and saved directly to the database, letting a regular user silently mark their own account as verified without ever going through whatever legitimate verification process (email confirmation, admin approval, etc.) is supposed to gate that field. This is the exact Django equivalent of the mass assignment vulnerability the Rails course's Forms & Strong Parameters chapter warned about — a form accepting more fields than the developer intended to expose. WHY THIS WORKS -------------- - fields = "__all__" tells ModelForm "generate a form field for every single field on the model" — convenient, but it means the form's writable surface grows automatically (and silently) every time a new field is added to the model, including sensitive ones like is_verified, is_staff, or is_superuser that were never meant to be user-editable through this particular form. - An explicit fields = [...] list is a genuine allowlist: ModelForm simply never generates a field for anything not named in that list, so no amount of extra data in the submitted POST body can set is_verified through this form — the field isn't part of the form's validation or save() logic at all, regardless of what a malicious request contains. - This is the same principle behind every allowlist-based defense covered elsewhere in this project (Rails' strong parameters, the OWASP course's "deny by default" access-control chapter) — explicitly stating what's allowed is safer than trying to explicitly block what isn't.