Login

Prevent form tampering of hidden fields

Author:
theju
Posted:
December 13, 2009
Language:
Python
Version:
1.2
Tags:
forms hidden tampering
Score:
1 (after 1 ratings)

Sometimes, we need to pass hidden fields with an initial value in forms but cannot trust the returned values because it could have been tampered.

So here is a form that adds an additional 'hidden' field (called 'form_hash') that hashes all the initial value of hidden fields and checks for tampering.

Pretty straightforward to use.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
from django import forms
from django.conf import settings
try:
    from hashlib import md5
except ImportError:
    from md5 import md5

class SecureForm(forms.Form):
    """
    >>> from django import forms
    >>> class TF(SecureForm):                         
    ...    name  = forms.CharField(max_length=10)     
    ...    age   = forms.IntegerField(widget=forms.HiddenInput())
    ...    email = forms.EmailField(widget=forms.HiddenInput(), required=False)
    ...                                                                        
    >>>
    >>> t = TF(initial={'age':20})
    >>> p = TF({'name':'theju','age': 30, 'form_hash': t.initial['form_hash']})
    >>> p.is_valid()
    False
    >>> p.errors
    {'__all__': [u'Form has been tampered!']}
    >>> p = TF({'name':'theju','age': 20, 'form_hash': t.initial['form_hash'], 
    ...         'email': 'test@example.com'})
    >>> p.is_valid()
    False
    >>> p.errors
    {'__all__': [u'Form has been tampered!']}
    >>> t = TF(initial={'age': 20, 'email': 'test@example.com'})
    >>> p = TF({'name':'theju','age': 20, 'form_hash': t.initial['form_hash'], 
    ...         'email': 'test@example.com'})
    >>> p.is_valid()
    True
    >>> p = TF({'name':'theju','age': 20, 'form_hash': 'nonsense', 
    ...         'email': 'test@example.com'})
    >>> p.is_valid()
    False
    >>> p.errors
    {'__all__': [u'Form has been tampered!']}
    >>>
    >>> class TestForm(SecureForm):
    ...    name = forms.CharField(max_length=10)
    ...    age  = forms.IntegerField()
    ...
    >>> t = TestForm({'name':'theju','age':50})
    >>> t.is_valid()
    True
    >>> t.errors
    {}
    >>> t = TF(initial={"name": "theju", "age": 20, "email": "test@example.com"}, 
    ...        exclude_fields=["email"])
    >>> t.initial['form_hash']
    '98a7d8b771644c7e86717c2e03d8f0a9'
    >>> p = TF({"name": "theju", "age": 30, "email": "test@example.com", 
    ...         "form_hash": t.initial["form_hash"]}, exclude_fields=["email"])
    >>> p.is_valid()
    False
    >>> p.errors
    {'__all__': [u'Form has been tampered!']}
    >>> p = TF({"name": "theju", "age": 20, "email": "test1@example.com", 
    ...         "form_hash": t.initial["form_hash"]}, exclude_fields=["email"])
    >>> p.is_valid()
    True
    >>> p.errors
    {}
    >>> p = TF({"name": "theju", "age": 20, "email": "test1@example.com", 
    ...         "form_hash": t.initial["form_hash"]}, exclude_fields=("email",))
    >>> p.is_valid()
    True
    >>> p.errors
    {}
    """
    def __init__(self, *args, **kwargs):
        exclude_fields = kwargs.pop("exclude_fields", None)
        super(SecureForm, self).__init__(*args, **kwargs)
        form_hash = forms.CharField(widget=forms.HiddenInput(), required=False)
        self.fields.update({'form_hash': form_hash})
        self.exclude_fields = ["form_hash"]
        if exclude_fields and isinstance(exclude_fields, (list, tuple)):
            self.exclude_fields += list(exclude_fields)
        hash_str = u""
        for name, field in self.fields.items():
            if field.widget.is_hidden and name not in self.exclude_fields and (self.initial.get(name, None) or field.initial):
                hash_str += unicode(self.initial.get(name, None) or field.initial)
        if hash_str:
            hash_val = md5(hash_str.encode("utf-8") + settings.SECRET_KEY).hexdigest()
            self.initial['form_hash'] = hash_val

    def clean(self):
        cleaned_data = super(SecureForm, self).clean()
        hash_str = u""
        for name, field in self.fields.items():
            if field.widget.is_hidden and name not in self.exclude_fields and cleaned_data.get(name, None):
                hash_str += unicode(cleaned_data[name])
        if hash_str:
            hash_val = md5(hash_str.encode("utf-8") + settings.SECRET_KEY).hexdigest()
            if hash_val and hash_val != cleaned_data['form_hash']:
                raise forms.ValidationError("Form has been tampered!")
        return cleaned_data


if __name__ == '__main__':
    import doctest
    doctest.testmod()

More like this

  1. Tamper safe HiddenFields by alexmeisel 6 years, 9 months ago
  2. Load initial form fields from GET parameters by ramen 5 years, 9 months ago
  3. SignedForm: CSRF-protect forms with a hidden token field by exogen 6 years, 11 months ago
  4. Field value as plain text which can't be edited by user by szczavv 5 years, 11 months ago
  5. ShowOnly widget for froms by alexmeisel 6 years, 9 months ago

Comments

Please login first before commenting.