Skip to content

Commit a6a89f2

Browse files
committed
Fix MFA lockout bypass via session user id mismatch
The lockout check only applied when the session's tracked mfa_fail_user_id matched the user currently logging in, so an attacker could clear another account's active lockout early simply by logging in as a different user first. Enforce the lockout regardless of which user triggered it.
1 parent dc20015 commit a6a89f2

1 file changed

Lines changed: 6 additions & 2 deletions

File tree

source/app/business/auth.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,8 +147,11 @@ def wrap_login_user(user, is_oidc=False):
147147
# here — an attacker who re-POSTs /login mustn't be able to zero
148148
# out the fail counter and get a fresh burst of 5 tokens.
149149
locked_until = session.get('mfa_lockout_until')
150-
locked_user_id = session.get('mfa_fail_user_id')
151-
if locked_user_id == user.id and locked_until and locked_until > time.time():
150+
if locked_until and locked_until > time.time():
151+
# An active lockout exists in this session, regardless of
152+
# which user triggered it. Logging in as a different user
153+
# (even the attacker's own account) must not be able to
154+
# clear another account's lockout early.
152155
flash('Too many attempts. Please try again later.', 'danger')
153156
return redirect(url_for('login.login'))
154157

@@ -160,6 +163,7 @@ def wrap_login_user(user, is_oidc=False):
160163
if not same_failed_user:
161164
session['mfa_fail_count'] = 0
162165
session.pop('mfa_lockout_until', None)
166+
session.pop('mfa_fail_user_id', None)
163167
session.pop('pending_mfa_secret', None)
164168
return redirect(url_for('mfa_verify'))
165169

0 commit comments

Comments
 (0)