Skip to content
Newer
Older
100644 82 lines (57 sloc) 3.03 KB
33f34ae Added a cookbook example of simple CSRF protection
Nimrod S. Kerrett authored Mar 11, 2011
1 ---
2 layout: default
3 title: How to protect forms from CSRF attacks
4 ---
5
6 # How to protect forms from CSRF attacks
7
8 ## Problem
9
10 How to make sure a POST form submission genuinely originates from a form created by the application,
11 and is not a [Cross-Site Request Forgery](https://secure.wikimedia.org/wikipedia/en/wiki/Csrf).
12
13 ## Solution
14
15 We keep a unique csrf_token that is rendered as a hidden field inside post forms and can not be guessed by CSRF attackers.
16 This token gets checked during POST methods.
17
18 We need 4 things:
19
20 1. A `csrf_token()` function - to use inside form templates. It either returns the existing `session.csrf_token` or generates a new one.
21
22 1. A `@csrf_protected` decorator for `POST()` methods. It pops `session.csrf_token` and compares it with the `csrf_token`
23 input we expect to get from a genuine form (see `<input type="hidden" ...>` below.
24 Whether the test succeeds or fails, this will make sure that next time `csrf_token()` is called (most probably - from
25 inside a form's template), a new token will be generated.
26
27 1. Make `csrf_token()` available to templates by adding it to the globals of our `render` object.
28
29 1. Add `<input type="hidden" name="csrf_token" value="$csrf_token()"/>` to the forms in the templates.
30
31
32 We define `csrf_token()` like this:
33
34 def csrf_token():
35 if not session.has_key('csrf_token'):
36 from uuid import uuid4
37 session.csrf_token=uuid4().hex
38 return session.csrf_token
39
40 The `@csrf_protected` decorator is defined like this:
41
42 def csrf_protected(f):
43 def decorated(*args,**kwargs):
44 inp = web.input()
45 if not (inp.has_key('csrf_token') and inp.csrf_token==session.pop('csrf_token',None)):
46 raise web.HTTPError(
47 "400 Bad request",
48 {'content-type':'text/html'},
49 """Cross-site request forgery (CSRF) attempt (or stale browser form).
50 <a href="">Back to the form</a>."""') # Provide a link back to the form
51 return f(*args,**kwargs)
52 return decorated
53
54 In order to make csrf_token() available to templates, we need to add it to the globals of the `render` object like this:
55
56 render = web.template.render('templates',globals={'csrf_token':csrf_token})
57
58 A template that renders a POST form (called - say - `myform.html`) would look like:
59
60 <form method=post action="">
61 <input type="hidden" name="csrf_token" value="$csrf_token()"/>
62 # ... form fields ...
63 </form>
64
65 If we're using a `Form` object from `web.form` called _form_, our `myform.html` template would look like:
66
67 <form method=post action="">
68 <input type="hidden" name="csrf_token" value="$csrf_token()"/>
69 $:form.render()
70 </form>
71
72 The form page's object would then look like:
73
74 class myformpage:
75 def GET(self):
76 return render.myform(...)
77 @csrf_protected # Verify this is not CSRF, or fail
78 def POST(self):
79 # If we're here - this is not a CSRF attack
80
81 A simple working demo is availale [here](https://gist.github.com/857297).
Something went wrong with that request. Please try again.