Account enumeration is the process where someone attempts to find out which accounts are valid users in a system, by using typically the login, registration or password reset pages of a system in a way that it wasn't designed for. The most basic example of a potential issue when it comes to account enumeration, is the response delivered when attempting to log in with either a wrong username or password.
Go ahead and try this yourself if you'd like: enter either a wrong username or an invalid password for your own valid username on a website where you have an active account. What response do you get back? Hopefully, the answer is something in the lines of:
"Invalid username or password"
But if the website returns a response like invalid username when entering an unknown username, or invalid password when entering your own username? Then you know that in the second case, the username is valid. Which means that someone with less noble intentions can now attempt to contact you because they know you have an account on this website or service.
But what about registrations?
Showing a uniform response to bad login attempts is easy. But in a registration form, you may run into more issues when attempting to thwart account enumeration vulnerabilities.
When we implemented our registration form, we check for the following things in no particular order:
- the username is entered
- the username looks like an email address, because it should be a valid email address
- the username is not used by another user
- the password is entered
- the password has between 8 and 100 characters
- the password meets the complexity requirements (a-z, A-Z, 0-9, punctuation characters)
- the confirmation password is entered
- the confirmation password matches the first password field's value
One of these validations is a bit more special: the fact that the username is not used by another user. You can't just validate against this and show an error like:
We're sorry, but the username "mike.wazowski@pixar.com" has already been taken
A validation message like this would make it very obvious that this username is known in the system! Instead, when all fields contain valid input, but the username already exists, we:
- Show a confirmation that the registration succeeded, and that the user should check their inbox to activate their account.
- Send an email to the already registered user that someone tried to register an account using their email address, and, in case they did this, remind them they already have an account.
Always test your forms
This all sounds fine, until we started rigorously testing our registration form. Here's the input we used and the resulting validation errors or confirmation:
Username | Password | Result |
<new account> | <too short> | Your password should be between 8 and 100 characters in size. |
<existing account> | <too short> | Your password should be between 8 and 100 characters in size. |
<new account> | <right size but only lowercase letters> | Your password should contain a combination of lowercase, uppercase, digits and punctuation characters. |
<existing account> | <right size but only lowercase letters> | Your account has been created, please check your inbox to activate your account. |
Whoops! Apparently, the password complexity rules were being checked during the creation of the user account, and not before we checked if the username was already taken! This different result means that someone could try to find out who already has an account based on the different validation response!
Luckily, we quickly identified and mitigated this issue before the registration form went live.