(How) does Django prevent data injection by manipulating forms?

I'm using Django's built-in User model in one of my projects. Users should be editable, of course. Since it's the most convenient solution I started providing the form for User edits by using Django's own UserChangeForm. UserChangeForm is basically a ModelForm of the UserModel, so it makes it possible to change all the fields of the User model.

I don't want users to be able to change every field. So, my first idea was to pass the whole UserChangeForm into the template but render only the fields that I need (say 'username' and 'email'). I also want only superusers to be able to change a username, so this field is only rendered if you are a superuser.

The essential code looks like that:

view function

def edit_user(request, pk):
    ...    #code to ensure not everyone can edit every user
    user = User.objects.get(pk=pk)
    if request.method == 'POST':
        form = UserChangeForm(request.POST, instance=user)
        if form.is_valid:
           ...    #redirect
        form = UserChangeForm(instance=user)
    ...    # render template

form in the template

<form action="{{ request.path }}" method="post">
    {% csrf_token %}
    {% if user.is_superuser %}
        {{ form.username }}
    {% endif %}
    {{ form.email }}
    <button type="submit">Save</button>

Now, my question is: How about the security aspects of this solution? I do nothing to prevent an attacker to add the e.g. the username field even if he is no superuser. By that, he would populate the POST data with additional data, which is then send to the view function and used to update the User object. That could get really dangerous, since the original UserChangeForm also contains a field 'is_superuser'.

I tried the hack the form myself to test my suspicion. I logged in as a normal user, added the username input via the Developer Tools and submitted the form. The result was an exception:

File "/Library/Python/2.7/site-packages/django/core/handlers/base.py" in get_response
  111.                         response = callback(request, *callback_args, **callback_kwargs)
File "/Library/Python/2.7/site-packages/django/contrib/auth/decorators.py" in _wrapped_view
  20.                 return view_func(request, *args, **kwargs)
File "/Users/joker/Development/wiki2099/wiki2099/apps/accounts/views.py" in edit_user
  69.             form.save()
File "/Library/Python/2.7/site-packages/django/forms/models.py" in save
  364.                              fail_message, commit, construct=False)
File "/Library/Python/2.7/site-packages/django/forms/models.py" in save_instance
  74.                          " validate." % (opts.object_name, fail_message))

Exception Type: ValueError at /accounts/edit/12/
Exception Value: The User could not be changed because the data didn't validate.

I'm not sure if that means that this kind of attack is not possible or that I just didn't do it right. I think, the CSRF token could prevent such kind of hacks, but I found nothing about that matter in the docs. Could anyone enlighten me? Is there any mechanism to prevent attackers from using not-rendered form fields and how does it work?

Thanks in advance!


If you want to restrict the fields that the user can edit, then you need to define a form with a subset of the model's fields.

If you don't render a form field in the template, but the user submits data for it, then Django will process it as normal. Having looked at the traceback, I don't understand why your attempt failed, but an attack using the method you described is possible.

The csrf protection won't help you here. Its purpose is to prevent a third party tricking your users into submitting data to your site, not protect against hand crafted POST data with extra fields.

There's another issue to be aware of if you deliberately don't render fields in the template: If the omitted field is not required, then the missing POST values will be interpreted as empty strings, validate, and your data will be wiped.

As far as I know, CSRF token only protects from CSRF atacks and doesn't do any things about permissions to change data.

Though it isn't clear if you only not render the field in case the user is not a superuser, or you didn't add it to self.fields at all. You should use the second way, or even have a separate form for superusers.

Need Your Help

Unit Testing SiteMapNode

unit-testing tdd mocking sitemap

Does anyone know how to unit test SiteMapNode? We’re building some custom navigation controls, which renders unordered html lists from Site Maps with custom attributes.

change browser's blue default color when selecting an option element in a select element

javascript css browser

I know how to change the color of the background of the options element, and also the hover.