Unify authentication errors.

Give the same error if the username doesn't exist or if the password
is wrong.  If we deliver separate errors, we tell the attacker whether
they've picked a valid password or not.

Also, if username doesn't exist, hash the password anyway to avoid
this timing side-channel attack:

1. Invalid Username:

   A. User tries to log in with invalid username.
   B. User name is not found in database.
   C. Password is never hashed.

2. Invalid Password:

   A. User tries to log in with valid username.
   B. User name is found in database.
   C. Password is hashed.

Given that proper password hashing will take a minute, *not* hashing
the password takes so much less time that we've effectively indicated
to the attacker that the username didn't exist, regardless of the
error message.  This way, no such error occurs.
This commit is contained in:
Nick Daly 2013-03-23 19:59:20 -05:00
parent ea49a08675
commit 1492fe9728

View File

@ -11,6 +11,7 @@
import cherrypy import cherrypy
import urllib, hashlib import urllib, hashlib
import cfg import cfg
import random
cfg.session_key = '_cp_username' cfg.session_key = '_cp_username'
@ -18,29 +19,28 @@ def check_credentials(username, passphrase):
"""Verifies credentials for username and passphrase. """Verifies credentials for username and passphrase.
Returns None on success or a string describing the error on failure""" Returns None on success or a string describing the error on failure"""
start = time.clock()
if not username or not passphrase:
error = "No username or password."
cfg.log(error)
return error
u = cfg.users[username] u = cfg.users[username]
if u is None:
cfg.log("Unknown user: %s" % username)
return u"Username %s is unknown to me." % username
if u['passphrase'] != hashlib.md5(passphrase).hexdigest():
return u"Incorrect passphrase."
elif u is None:
def check_auth(*args, **kwargs): # hash the password whether the user exists, to foil timing
"""A tool that looks in config for 'auth.require'. If found and it # side-channel attacks
is not None, a login is required and the entry is evaluated as a hashlib.md5(passphrase).hexdigest()
list of conditions that the user must fulfill""" error = "Bad user-name or password."
conditions = cherrypy.request.config.get('auth.require', None) elif u['passphrase'] != hashlib.md5(passphrase).hexdigest():
if conditions is not None: error = "Bad user-name or password."
username = cherrypy.session.get(cfg.session_key)
if username:
cherrypy.request.login = username
for condition in conditions:
# A condition is just a callable that returns true or false
if not condition():
raise cherrypy.HTTPRedirect("/auth/login")
else: else:
raise cherrypy.HTTPRedirect("/auth/login") error = None
if error:
cfg.log(error)
return error
def check_auth(*args, **kwargs): def check_auth(*args, **kwargs):
"""A tool that looks in config for 'auth.require'. If found and it """A tool that looks in config for 'auth.require'. If found and it