One of the most common and underestimated web application vulnerabilities I find frequently is user enumeration. Simply put, I can figure out a list of valid user accounts that are allowed to login to an application. This isn’t just assuming there’s a common user called “Admin” and attempting to guess the password for that account. Instead, this is the ability to compile a list of valid users based on a flaw in the registration process, login sequence, or password reset functionality.
It may seem relatively easy to avoid this type of vulnerability: don’t tell the person registering, logging in or resetting their password that the username already exists. Just display a generic message that tells them in a friendly way “you’re either confused or you don’t belong here”. So, why is it that so many applications still contain this vulnerability? Below is a list of five common (not exhaustive) scenarios that I’ve seen in the past year, and a short description on how to prevent an attacker from enumerating your users.
*Side note: “Username” is synonymous with email address, customer identifier, User ID, etc.
Issue: A single-factor (username and password) login form reveals whether or not a username is valid based on the error message returned. For example:
- User Exists: Sorry, the username you have entered does not exist
- User Does Not Exist: Sorry, the password you have entered does not match that username
Solution: Return a generic, friendly error message such as “Sorry, the username or password entered does not exist”
C is for Cookie (and also half of your credentials)
Issue: A cookie is set when a username is valid and not set (or set differently) when a username is invalid. This usually occurs when just the username is required to start the login sequence. After the username is entered, the user is the prompted for security questions. These security questions are designed to display regardless of whether the username entered is in the database, attempting to prevent user enumeration. It’s transparent to the user, but a cookie value is set and gives the attacker a way to determine if the username is valid. For example:
- User Exists: Set “User” cookie = 138298432 (some random 9 digit value)
- User Does Not Exist: Set “User” cookie = 0
Solution: When a user does not exist, set the “User” cookie to a fake 9-digit random value or don’t set that cookie at all unless required for the application.
Meet the New User, Same as the Old User?
Issue: Applications require a unique username or email address when registering. During the registration process or when a user wants to change their username or email address, the application alerts them when a username or email already exists, prompting them to select another. For Example:
- User Exists: Sorry, that email already exists, please select another email address
- User Does Not Exist: Congratulations! Your new email address has been set!
Solution: This one might seem tricky and can be done with or without a CAPTCHA type sequence. If the username is an email address, require a one-time, expiring link for email address changes. When a user wants to change their email address, follow the example process flow:
- Send a link to the existing email address that expires upon clicking and after a specified amount of time. That link allows them to change their email address.
- If the user selects an email that already exists within the system, do not alert the user that the email already exists, but rather display a message similar to “Thank you, a notification email has been sent to that email address.”
- Send an email to the new email address advising the user that there was an attempt to register their email address with the application, but the action could not be completed since the email is already registered. For additional security, send a suspicious activity alert back to your web security team.
- If the user selects an email address that is not already registered, send the standard email address change link.
- If the username is not an email address, but rather something the user creates on their own, a CAPTCHA or similar technology can be used to limit the speed in which the usernames are enumerated, but not eliminate the attack entirely.
Not So Secret Questions
Issue: The forgot password functionality presents “secret questions” when a valid username is entered, but displays an error when an invalid username is entered. A slight modification to this one is secret questions are displayed for invalid users, but they’re always the same, canned questions for every invalid user.
Solution: This solution has a prerequisite. If your application uses security questions, you should collect answers for at least 5 open-ended questions during the registration process. These questions should not prompt for answers that can be easily found on Facebook, Google, LinkedIn, etc. Display and randomize these security questions when a user enters either a valid or invalid username. This will prevent attackers from determining a valid username. Quick examples of good questions:
- Who was your childhood hero?
- What was your favorite vacation?
Polly Want a Username?
Issue: A username field is required before proceeding to the password screen. A valid username leads to a page with a custom image and phrase or canned image (why is there always a Parrot?) and custom phrase. An invalid username leads to a page with a canned image and phrase.
First of all, that image and phrase is assisting the attacker more than the actual user. It’s supposed to tell the user that they might be on an imitation website that is harvesting their credentials since the image and phrase don’t match up. In my opinion, if they’ve made it that far in the process, they’re going to keep on typing as long as there is some picture and phrase there. The attacker, on the other hand can easily recognize the canned picture and phrases as the phrases are ones that nobody would ever type; and usually grammatically correct.
Solution: If possible, get rid of the image and phrase and opt for security questions or a two-factor solution like SMS or token. If you’re stuck with the image and phrase, then only allow the user to select an image from a set of images provided to them and phrases from a drop-down list. Allowing them to create their own phrase or upload an image is a dead giveaway for an attacker that the username is valid. The key for invalid users here is consistency. If I’m an attacker and enter in “test” and get a different image and phrase each time, then I know that user is invalid. A valid user will always be presented the one they chose. Therefore, you must set a consistent image and phrase for invalid users as they’re attempted.