On the first authentication system that I ever wrote I made an innocent mistake that, thankfully, was exposed by QA. The authentication error message when an account was locked out was slightly different depending if the entered password was correct or incorrect. The vulnerability there was that a brute force attempt could cycle through all of the passwords until it found the right one. The lockout would have engaged but the attacker could simply wait for the account to be unlocked. In my defense I was trying to give the user as much information about the source of their denied access as possible.
This really made me examine the whole issue of the authentication error message. The most secure thing would simply be to error with "Invalid" for any circumstance. This would prevent any brute force attempt from gaining any information whatsoever. This however, would be a serious usability issue resulting in frustrated users making unneeded password resets or help desk calls. There is a healthy balance though, between the two extremes. For instance the 28 potential error messages in Sun Java System Access Manager give away WAY too much information that, while appreciated by a user, can be abused by a brute force script.
So in the authentication error message we have a clear case of compromising security for usability.
In my opinion the following three error messages are the only error messages that should be provided to the user for a secure authentication system:
"Incorrect Username and/or Password"
This error lets the user know they have failed authentication without giving away the source of the mistake. It could be an unknown account or it could be a known account with an incorrect password. It's important that that the message not be different when the account is unknown. This is also the error message to use if the username or password does not pass validation (null, length, invalid characters) as we don't want to provide a method for a brute force script to understand what we consider valid looking usernames and passwords, thereby narrowing the search space.
"The requested account is locked out, please contact support"
Informing the user of a lockout has to be done to be usable. After all, we can't have the user keep trying forever :) Lockout too is a necessary feature to prevent brute forcing. This could be a permanent lockout or a time-based lockout to slow the attempt to brute force. The lockout is typically enacted after 3-5 failed authentication attempts. Assuming the error message is neutral after the lock out, this effectively prevents brute forcing of the passwords. But there is also the issue of brute forcing the username. The authentication system should simulate the lockout of usernames it does not know as well. This isn't very complex and can be accomplished by retaining an in-memory map of unknown user names to attempts (which can be limited in size to prevent memory DoS attack).
"An error occurred, please contact support"
This is another usability concession. If we really can't perform authentication we need to let the users know that something has to be done. This should only be for situations where the issue can be corrected (like connectivity to the database, directory or SSO), not for exceptions based on the input (like length, invalid characters or an invalid request) since again, we would give away information on what we would consider valid looking input for the username and password.
Other errors can be presented to the user if the authentication is correct and the account is not locked out. These don't present a security risk since the authentication is valid and the reason for the failure is circumstantial:
- The maximum number of concurrent sessions are in progress
- Cookies must be enabled
- Unable to redirect
- and others...
It's also important that no additional insight should be able to be gained by a brute force script by looking at the response (webservice, html, etc). For instance, error codes and raw exceptions are a big no-no. This is particularly a problem in API's like a Webservice, where it may be Internet-exposed and convenient to pass on raw errors. If the underlying authentication gives more detail it has to be wrapped in a generic error to hide the details.
For administrators of the authentication system, much more information on the reasons for the invalid authentication can be logged for further review as long as this information is stored and accessed securely (out of band). The detailed breakdown is important if they are truly trying to diagnose an issue with the authentication system.
While, as programmers, we want to give the user as much information as possible, it's vital to know when to be deliberately obscure.
