While It may not strictly fit the definition of "timing attack" I'm still concerd about this.
I have a pyramid application which exposes a login view. Whenever someone enters a non-existent user, the application shows a message saying that the login/password combination is invalid. This also happens when a valid user is entered but the password is invalid.
The purppose of this is to avoid giving a hypothetical attacker any information about a user's account.
The problem is that, in the second case, due to the fact that it has to check if the password is valid, the program takes a noticeably longer amount of time. This effectively renders the aforementioned scheme invalid since the attacker can guess if the user exists based on the amount of time elapsed between the posting of the form and the error message.
Is there a way to mitigate this issue? Or is it better to just leave the authentication code as it is?
Here is the code for the login view.
@view_config(route_name='login', renderer='templates/login.mak')
@forbidden_view_config(renderer='templates/login.mak')
def login(request):
form = LoginForm(request.POST, meta={"csrf_context": request.session})
login = form.user.data
password = form.password.data
if request.POST:
if form.validate():
user = DBSession.query(Users).filter(Users.username == login).scalar()
if (user is not None) and checkUser(user, password):
headers = remember(request, login)
return HTTPFound(location="/", headers=headers)
else:
# The login failed: there is no such user, or the password is invalid.
raise exc.HTTPForbidden()
else:
raise exc.HTTPForbidden()
return {'form': form}
Thanks in advance.
The timing issue originates from the if user is not None
check, which will cause checkUser
not to run. You'll need to do some work that is preferably as close as possible as the amount of work you're doing when checking a valid user account.
I would remove the is None
check in your if and patch checkUser
to also work in the case user = None
. In that case you would probably (depending on your implementation of checkUser
) have to do something like Django is doing, namely create an empty user object with some password and run the password hasher on that, so you still do roughly the same amount of work.
(This answer (specifically the django reference) is partially based on jedwards's comment)