Username enumeration within web applications is a solvable problem – but I often see web administrators either ignore the issue because they don’t think it’s significant enough to address, or they think it’s not possible to fully address it. So, let’s explore the difficulty here, and I’ll give some examples of when enumeration is a problem and some examples of how to fix it.
First of all, what is username enumeration? It is when a web application has a feature that allows a user to supply a username and the application will disclose (not necessarily intentionally) if the username is valid or not. This is closely related to Username Disclosure, except in the latter the application is including valid usernames in server responses in some way, which allows a threat actor to determine a username is valid without having to specify it first themselves. Both of these are an issue and both should be addressed.
There are two sides to this: Privacy and Security. On the privacy side of things there are some applications (for example, healthcare or adult entertainment) where a user may consider the fact that they have an account on that application as confidential. Therefore, if a threat actor can gain a list of usernames, or even just confirm that a specific individual is a user, this could be a privacy matter.
Secondly, there is the security side. When it comes to password guessing attacks, if an attacker has to individually guess both usernames and passwords then a large amount of their password guesses will likely be for user accounts that don’t exist, which is inefficient. If the attack is slow enough this additional burden may in fact be enough for the attack to fail overall, or at the least, for the number of compromised accounts to be reduced. However, if an application can be caused to disclose that a username is valid, then an attacker can first produce a list of valid usernames and then attempt to bruteforce passwords for them – leading to a much more efficient attack.
The most common place to see this is login, registration, and forgotten password functionality. For example, if I can enter an email address into the forgotten password field and I am shown one of two possible outcomes:
Then I can use this form to supply a list of potential email addresses, and the application will disclose to me whether or not they are valid. Same for the login page, if it says “incorrect username” or “incorrect password”, I can use the difference in messages to determine if an account exists or not.
The same could apply to a login form, where the application gives the response “Incorrect username” or “Incorrect password”, the latter of which allows the user to infer that the supplied username must have therefore been correct. A simple solution to this one, is generic error messages. The application could simply say “Incorrect credentials”, thereby mitigating the disclosure.
Note that the output doesn’t have to be that explicit. For example, if an application always returns “Invalid credentials” instead of “invalid username” or “invalid password” that doesn’t mean enumeration isn’t possible. For example, if the application takes 5 seconds to process and deny a login request for a valid username, but only 3 seconds to process and deny a login request for an invalid username – a threat actor can still enumerate account validity, even though the error message itself doesn’t disclose this, via monitoring the server response times. Therefore, all aspects of the server’s response (message content, content length, response time, etc) must be the same to prevent enumeration.
Additionally consider more complex user flows, such as account registration or a checkout process that lets a user register an account during checkout. How do we prevent enumeration whilst also reducing friction for the user.
The example given above of simply including a generic error message work for most instances of enumeration, but notable won’t work on flows such as account registration. I often hear people state that username enumeration can’t be fixed in registration forms because you can’t simply allow multiple account registrations with the same email address. You also can’t simply present an error informing the user that the email address is taken or even a generic error message saying “something went wrong” as the presence of the error would disclosure the validity of the account.
To address this issue requires a slightly different registration flow instead, preventing the enumeration but also ensuring as minimal friction to the user as possible. For example, consider a registration process where a user is required to enter their email address and we want to prevent enumeration of valid email addresses. Consider the following registration flow:
Allow the user to complete the registration page with their chosen email address and then present an interstitial message saying that the user has been sent an email and they must confirm their email address is valid (this is typically done by having a validation code in the email, or simply a link for the user to click in order to continue the process).
With a flow like this, preventing enumeration is very simple. If a user registers with an email address that hasn’t been used before then the process is as described above. However, if a user registers with an email address that is already in use, instead of emailing them a validation code – you simply send a message informing them that an account already exists (and it’s probably a good idea to include details of how to reset the password, since if they’ve forgotten they had an account they’ve probably also forgotten their account password).
With this minor modification, registration proceeds as normal, but consider it from the point of view of a threat actor trying to enumerate the validity of email addresses. In either case, the address being in use or the address not being used, the attacker is simply presented with a message saying words to the effect of “Thank you for registering, please check your email for a confirmation email.” Thereby fixing the enumeration, as there is no difference in response that can be used to determine the account validity.
One last thing to add though, is you should also consider detaching the access credentials from all other account functionality. Where possible the username should only be used during login and not used elsewhere within the application. For example, if a user logs in with their email address, then they should be referred to by their full name or username within the application and their email address should not be used as a public identifier within the application wherever possible. The general idea here is that simply the fewer places where the username is requested or disclosed, the fewer places that you have to work through solving the username enumeration issue described above.