Best practices say that when users choose a password (at signup or when changing an existing password), the application should reject that password if it appears on a list of passwords known to be unsafe. For example, NIST Special Publication 800-63 from 2017 says the following (emphasis mine):
When processing requests to establish and change memorized secrets, verifiers SHALL compare the prospective secrets against a list that contains values known to be commonly-used, expected, or compromised. For example, the list MAY include, but is not limited to:
- Passwords obtained from previous breach corpuses.
- Dictionary words.
- Repetitive or sequential characters (e.g. ‘aaaaaa’, ‘1234abcd’).
- Context-specific words, such as the name of the service, the username, and derivatives thereof.
If the chosen secret is found in the list, the CSP or verifier SHALL advise the subscriber that they need to select a different secret, SHALL provide the reason for rejection, and SHALL require the subscriber to choose a different value.
Let's say I implement this using Have I Been Pwned's "Pwned Passwords" API.
Now what happens if a password used by one or more of my users appears¹ in a data breach? Obviously, the password should now be considered "definitely unsafe"²: sooner or later, attackers will include that password in credential stuffing or password spraying attacks.
Following the NIST advice, my application would reject entering such a password on a "change password" form (see above: "When processing requests to (...) change memorized secrets, verifiers SHALL (...)").
However, if nobody was alerted of that particular password being breached (neither me as the application owner nor every user using that password), that alone will not help: Attackers can still use that password until affected users have changed their password. But that may or may not happen. What's worse, for seldomly-used applications, a lot of time may pass until they log in the next time, so simply displaying a warning on login will not shorten the attack window.
Therefore, I believe that my application should reject login attempts which use a breached password:
- That way, an affected account would effectively be locked from the moment its password appears in a breach corpus. As no attacker could gain access, breach corpus passwords become useless to attackers.³
- The application would reject the login attempt with the same reaction (visible message and API response) as if the wrong password was used. This avoids giving the attacker the potentially useful information that this username/password could be used elsewhere.
- When a login attempt is rejected due to this, the application could send an email to the account owner informing them of the situation and asking them to use "reset password" functionality. The email would be sent asynchronously to minimize the risk of timing-based information leakage.
- To avoid repeated emails, the application would also set an appropriate flag on the account.
What I tried
I could not find any mention of rejecting login attempts related to breach corpuses. I thought about whether the word "establish" in the NIST guideline could mean "login", but I doubt that as the rest of that section would not really fit that interpretation.
When processing requests to establish and change memorized secrets, verifiers SHALL compare the prospective secrets (...)
Questions
- Is there any official/established/popular advice on the topic of rejecting login attempts due to passwords occuring in breach corpuses (as opposed to rejecting password change attempts)?
- Is such an approach advisable in general?
- Does this (my approach outlined above or the general idea) have any drawbacks or caveats one should consider?
Footnotes
For this, it doesn't matter whether it was indeed my user's data or someone else's data that appeared in the breach.
I am aware that just because a password does not appear on breach corpuses, it does not mean it should be considered safe.
Only the most fresh breaches, the ones that did not yet make it to HIBP, would still be useful.