Skip to content

Instantly share code, notes, and snippets.

@thedod
Created March 6, 2011 13:51
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save thedod/857297 to your computer and use it in GitHub Desktop.
Save thedod/857297 to your computer and use it in GitHub Desktop.
Webpy CSRF protection (request for comments)
A simple trick against CSRF for web.py (webpy.org)
* At the GET() template, you add a hidden field called csrf_token with value "$csrf_token()"
* The POST() should have the @csrf_protected decorator
That's it.
Request for comments:
* Is this secure? Can you see any holes?
* Is there anything in [or for] web.py that does this? Am I reinvevting the wheel here?
$def with(args)
<!doctype html>
<html lang="en">
<head>
<title>$args['title']</title>
</head>
<body>
<form method=post action="">
<input type=hidden name=csrf_token value="$csrf_token()">
<label for=message>Message:</label>
<input id=message name=message value="$args['message']">
<input type=submit value="Update">
</form>
$if args['veteran']:
If you <a href="javascript:history.back()">go back</a> and try to update the previous page,<br>
you'll be detected as a CSRF attack. Good luck :)
</body>
</html>
import web
urls = ("/", "testme")
app = web.application(urls, globals())
# Session/debug tweak from http://webpy.org/cookbook/session_with_reloader
if web.config.get('_session') is None:
session = web.session.Session(app, web.session.DiskStore('sessions'))
web.config._session = session
else:
session = web.config._session
def csrf_token():
"""Should be called from the form page's template:
<form method=post action="">
<input type=hidden name=csrf_token value="$csrf_token()">
...
</form>"""
if not session.has_key('csrf_token'):
from uuid import uuid4
session.csrf_token=uuid4().hex
return session.csrf_token
def csrf_protected(f):
"""Usage:
@csrf_protected
def POST(self):
..."""
def decorated(*args,**kwargs):
inp = web.input()
if not (inp.has_key('csrf_token') and inp.csrf_token==session.pop('csrf_token',None)):
raise web.HTTPError(
"400 Bad request",
{'content-type':'text/html'},
'Cross-site request forgery (CSRF) attempt (or stale browser form). <a href="">Back to the form</a>.')
return f(*args,**kwargs)
return decorated
# Note: in order to let templates use csrf_token, you need to add it to render's globals
render = web.template.render('.',globals={'csrf_token':csrf_token})
class testme:
def GET(self):
return render.index({ # I know it's not customary to pass a dict, but it's neater IMHO
'title':'WebPy CSRF example',
'veteran':False,
'message':session.get('message')})
@csrf_protected
def POST(self):
session.message = web.input().message
session.status = 'Updated message'
return render.index({ # I know it's not customary to pass a dict, but it's neater IMHO
'title':'Message updated',
'veteran':True,
'message':session.get('message')})
if __name__ == "__main__":
app.run()
@SmileyJames2
Copy link

@anandology I have implemented your first suggestion (adding a CSRF input field). And submitted a pull request (Pull Request #290 ) Unfortunately it uses sessions, perhaps someone could change that?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment