-
-
Save pigletfly/59f3d7aa7227a526f34e to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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? |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
$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> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment