Login

CSRF this!

Author:
oggy
Posted:
September 23, 2008
Language:
Python
Version:
1.0
Score:
0 (after 0 ratings)

A form with built-in CSRF protection. Include CsrfCookieMiddleware in your MIDDLEWARE_SETTINGS, subclass SafeForm and off you go. See: this django-developers post for more info.

[edit] This form is actually WAY overengineered currently. Will update soon.

 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
# forms.py
from django import forms
from django.conf import settings
import hmac
import hashlib
import base64
import time
from utils import random_chars
from csrf_settings import *

class SafeForm(forms.Form):
    # how long is the form valid (in seconds)
    FORM_TIMEOUT = 1800
    csrf_field = forms.CharField(widget=forms.HiddenInput)

    def __init__(self, request, *args, **kwargs):
        # must have CsrfCookieMiddleware enabled
        assert CSRF_COOKIE_NAME in request.COOKIES
        super(SafeForm, self).__init__(*args, **kwargs)
        if not self.is_bound:
            # create an initial value for the field
            tstamp = str(int(time.time()))
            salt = random_chars(SALT_LENGTH)
            cookie = request.COOKIES[CSRF_COOKIE_NAME]
            sig = tstamp + salt + self.__class__.__name__ + cookie
            sig = hmac.new(settings.SECRET_KEY, sig, digestmod=hashlib.sha1).hexdigest()
            final = base64.b64encode('/'.join([tstamp, salt, sig]))
            self.fields['csrf_field'].initial = final
        else:
            self._csrf_cookie = request.COOKIES[CSRF_COOKIE_NAME]

    def clean_csrf_field(self):
        tstamp, salt, sig = base64.b64decode(self.cleaned_data['csrf_field']).split('/')
        new_sig = tstamp + salt + self.__class__.__name__ + self._csrf_cookie
        new_sig = hmac.new(settings.SECRET_KEY, new_sig, digestmod=hashlib.sha1).hexdigest()
        if sig != new_sig:
            raise forms.ValidationError, 'You are under attack!'
        if int(time.time()) - int(tstamp) > SafeForm.FORM_TIMEOUT:
            raise forms.ValidationError, 'Form session has expired'

# middleware.py
from csrf_settings import *
from utils import random_chars

class CsrfCookieMiddleware(object):
    def process_response(self, request, response):
        if CSRF_COOKIE_NAME not in request.COOKIES:
            response.set_cookie(CSRF_COOKIE_NAME, random_chars(CSRF_COOKIE_LENGTH))
        return response

# utils.py
import string
import random

def random_chars(length):
    alphanum = string.ascii_letters + string.digits
    return ''.join(random.choice(alphanum) for i in range(length))

# csrf_settings.py
CSRF_COOKIE_LENGTH = 40
CSRF_COOKIE_NAME = 'no_csrf'
SALT_LENGTH = 4

More like this

  1. Template tag - list punctuation for a list of items by shapiromatron 10 months, 2 weeks ago
  2. JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 10 months, 3 weeks ago
  3. Serializer factory with Django Rest Framework by julio 1 year, 5 months ago
  4. Image compression before saving the new model / work with JPG, PNG by Schleidens 1 year, 6 months ago
  5. Help text hyperlinks by sa2812 1 year, 7 months ago

Comments

simon (on September 23, 2008):

Using middleware to set the CSRF cookie is a smart approach. I'm not sure if I like it though - I want CSRF protection to be ridiculously easy to turn on (just use SafeForm instead of regular Form) and most Django projects I work on end up with far too many middleware classes as it is. I'd rather avoid telling people to apply Yet Another middleware.

#

ludvig.ericson (on September 24, 2008):

I agree with Willison.

This middleware is going to add the CSRF cookie to every response.

As for random_chars:

def random_chars(n): return random.sample(string.letters, n)

#

Please login first before commenting.