From e22fdad2c40dab1fecfa40241b458a86153fc57f Mon Sep 17 00:00:00 2001 From: Max Goodman Date: Wed, 27 Feb 2013 15:00:26 -0800 Subject: [PATCH] CSRF mitigation hack: reject logins sent with a session cookie. Configured user agents and logging in as the same user account are exempted. --- r2/r2/controllers/api.py | 15 ++++++++++++++- r2/r2/lib/app_globals.py | 1 + r2/r2/lib/errors.py | 1 + r2/r2/lib/validator/validator.py | 5 +++++ 4 files changed, 21 insertions(+), 1 deletion(-) diff --git a/r2/r2/controllers/api.py b/r2/r2/controllers/api.py index 2ea5876bc..ffb737382 100755 --- a/r2/r2/controllers/api.py +++ b/r2/r2/controllers/api.py @@ -447,9 +447,22 @@ class ApiController(RedditController, OAuth2ResourceController): responder._send_data(modhash = user.modhash()) responder._send_data(cookie = user.make_cookie()) - @validatedForm(user = VThrottledLogin(['user', 'passwd']), + @validatedForm(VLoggedOut(), + user = VThrottledLogin(['user', 'passwd']), rem = VBoolean('rem')) def _handle_login(self, form, responder, user, rem): + exempt_ua = (request.user_agent and + any(ua in request.user_agent for ua + in g.config.get('exempt_login_user_agents', ()))) + if (errors.LOGGED_IN, None) in c.errors: + if user == c.user or exempt_ua: + # Allow funky clients to re-login as the current user. + c.errors.remove((errors.LOGGED_IN, None)) + else: + from r2.lib.base import abort + from r2.lib.errors import reddit_http_error + abort(reddit_http_error(409, errors.LOGGED_IN)) + if not (responder.has_errors("vdelay", errors.RATELIMIT) or responder.has_errors("passwd", errors.WRONG_PASSWORD)): self._login(responder, user, rem) diff --git a/r2/r2/lib/app_globals.py b/r2/r2/lib/app_globals.py index 110e57858..a792c0008 100755 --- a/r2/r2/lib/app_globals.py +++ b/r2/r2/lib/app_globals.py @@ -183,6 +183,7 @@ class Globals(object): 'case_sensitive_domains', 'reserved_subdomains', 'TRAFFIC_LOG_HOSTS', + 'exempt_login_user_agents', ], ConfigValue.choice: { diff --git a/r2/r2/lib/errors.py b/r2/r2/lib/errors.py index 96bc8fc1a..746e4570f 100644 --- a/r2/r2/lib/errors.py +++ b/r2/r2/lib/errors.py @@ -42,6 +42,7 @@ error_list = dict(( ('TOO_MANY_THING_IDS', _('you provided too many ids')), ('NOT_AUTHOR', _("you can't do that")), ('NOT_USER', _("you are not logged in as that user")), + ('LOGGED_IN', _("you are already logged in")), ('DELETED_LINK', _('the link you are commenting on has been deleted')), ('DELETED_COMMENT', _('that comment has been deleted')), ('DELETED_THING', _('that element has been deleted')), diff --git a/r2/r2/lib/validator/validator.py b/r2/r2/lib/validator/validator.py index b4dd68e8c..ef7dcc108 100644 --- a/r2/r2/lib/validator/validator.py +++ b/r2/r2/lib/validator/validator.py @@ -1091,6 +1091,11 @@ class VUname(VRequired): self.param[0]: "a valid, unused, username", } +class VLoggedOut(Validator): + def run(self): + if c.user_is_loggedin: + self.set_error(errors.LOGGED_IN) + class VLogin(VRequired): def __init__(self, item, *a, **kw): VRequired.__init__(self, item, errors.WRONG_PASSWORD, *a, **kw)