43

I'm working on the onboarding functionality of my web app and at some point I'm asking a new user to choose a username.

When the user gets to this point, I have his/her email and a cellphone number that is validated with an SMS and he/she has passed Google's no CAPTCHA reCAPTCHA.

I need to check if the username one chooses already exists and if it does, then the user is prompted to choose another one.

Upon browsing the web, I came across some pages that say implementing such functionality also provides attackers with a tool to check if a username exists.

What are the best practices for checking if a username exists?

Aaron Hall
  • 159
  • 1
  • 1
  • 9
frenchie
  • 1,101
  • 2
  • 12
  • 17
  • 4
    do you need usernames or can you just use email addresses? – Neil McGuigan May 16 '16 at 17:42
  • 2
    My app uses usernames, not email addresses as the username (so that the user can later change his email address) – frenchie May 16 '16 at 17:43
  • truth of the matter is that they can only check usernames they would already know. You need to mitigate against this in either case(trying randomly, and with a list they know) – Robert Mennell May 16 '16 at 18:10
  • Less user friendly perhaps, but what if you generate a username yourself? You've already got their email address. Send the username by email with a one time unique key link where the are required to enter a password. – Jeroen May 16 '16 at 18:20
  • 1
    It seems to me that one solution is to have them log in with their emails instead of their user names, and simple use the user names on the site itself. – childofsoong May 16 '16 at 18:35
  • 7
    I think email address, when used to log in on a site, should still be called a username. You can easily create another column and call it their contact email. The contact email can be updated at their leisure. At the end of the registration process just say "Thank you, we will send an account activation link to your email." On the backend, send the activation link if the username is unique. If it is not unique then just inform the user that a sign up was attempted with their email and the standard "If this was you then please login, if not then contact us, blah blah blah" – MonkeyZeus May 16 '16 at 19:53
  • Leaving the security aspects aside for the moment, you should not check. You should merely disallow duplicates when inserting, at the table definition stage. Otherwise there is a timing window problem. This is a general principle. – user207421 May 16 '16 at 20:30
  • 1
    @EJP There still needs to be a check. This would mean if someone tried to make an account with a username already in use they would have to guess what went wrong and correct it. The user needs to be notified why they cannot make an account. Otherwise this is a bad user experience. If they were set on using the username in question it would be easy for a user to assume the account creation is broken and give up. – Bacon Brad May 16 '16 at 20:42
  • 1
    @baconface what EJP is saying is don't check to see if there is a dup then OK do the insert but do the insert and if it fails on a dup then report that. The first check first then insert is a timing window issue – mmmmmm May 16 '16 at 20:44
  • 2
    @Mark I believe that would be less user friendly as every time he tries to register, he would be told that the username doesn't exist. Instead of it, asking him to choose a unique username first would IMHO be a better flow – Limit May 17 '16 at 08:03
  • I have once used a system where you registered and got a user id, and upon account verification (email sent etc.) you were allowed to chose a "screenname" that was unique. The api for that worked only with an active verified account and was rate limited per such account to really low values. Chosing a new screenname was also rated to once per month. – PlasmaHH May 17 '16 at 09:20
  • Once registered, will (other) usernames be visible? (Like [here](http://security.stackexchange.com/users) If so, going overboard on preventing username enumeration is probably not worth it -- just rate-limit the attempts. – TripeHound May 17 '16 at 10:30
  • @Limit I cannot understand you. If the insertion fails due to a duplicate key you can tell the user so. I don't know how you can manage to turn that into 'username doesn't exist'. It can't be a 'better flow' if it is vulnerable to the timing-window problem I mentioned that is at the bottom of this. – user207421 May 17 '16 at 22:45

10 Answers10

42

This source says that it is almost impossible to avoid user enumeration in this situation and delaying an attacker is the best you can do:

If you are a developer you might be wondering how you can protect your site against this kind of attack. Well, although it's virtually impossible to make an account signup facility immune to username enumeration, it is however possible to avoid automated username enumeration attacks against it by implementing a CAPTCHA mechanism.

However, I might have missed something - I'm also curious if anybody else has a better solution.

Lukas
  • 3,158
  • 1
  • 15
  • 20
  • 8
    Good idea: add a counter to test how many times a user tested the system. – frenchie May 16 '16 at 17:48
  • 8
    Another thing to consider is that at that point in signing up, (past initial CAPTCHA, past SMS and email verifications), they still shouldn't be able to check for a username without attempting to finalize the sign-up process. That is, no AJAX endpoint to say if a username is available, they should choose a username and either get it assigned to them, or get bounced back to try again. – Ghedipunk May 16 '16 at 17:54
  • 14
    @Ghedipunk from a security standpoint that might be good, but from a UI/UX design point it's terrible. I would say a middle-ground of the first $x checks from a given IP are "free" (ajax'ed in), but after that the checks need to be accompanied by a captcha response. By that stage I've already got that username and password in my password manager, and you're telling me _way too late_ that my chosen username isn't available. It would feel like a bait and switch and I bet you would have a high rate of abandoned signups. – Mark Henderson May 17 '16 at 11:18
  • @Ghedipunk, I'm not understanding the difference. Is it that AJAX is more responsive, and would allow for faster data skimming? Or are we assuming that an attacker would not be willing to register users? – Mathieu K. May 17 '16 at 17:05
  • 1
    I suggest explicitly incorporating @frenchie 's idea into this answer. Here's why: if the form starts requiring a captcha after the Nth attempt, and provided that the captcha is doing its job, it not only slows down the attack, it forces the attacker to use a human agent rather than a fully-automated attack. – Mathieu K. May 17 '16 at 17:17
  • @MarkHenderson I don't understand why. What kind of sign-up form are you envisioning? – Random832 May 17 '16 at 19:58
  • 1
    @Random832 Ghedipunk has suggested that you don't get notification that your chosen username is invalid until _after you've completed your signup and validated your email address_. If that happened to me, I would ragequit and decide to do business elsewhere. – Mark Henderson May 17 '16 at 20:07
  • 4
    @MarkHenderson: That's not how I read Ghedipunk's suggestion: I took it to mean that, once a user (whose cellphone number's already verified, so we have some guarantee of their identity) inputs their chosen username, then either (a) the username is already in use, in which case they get bounced & must try again, or (b) that username is then permanently associated with that user (based on their cellphone number). Thus they can't use the login process to scan large numbers of usernames for existence: as soon as they hit one that doesn't exist, it's game over (for that cellphone no. at least!). – psmears May 17 '16 at 21:02
  • Are false positives ok? If so: check if the username exists: if yes, say so. If no, if somehash(username, some-static-salt) has first bit set, say the username exists. This will make it look like half of all usernames are already taken, without telling the prospective user if there's actually a human behind a given username. This will prevent the prospective user from trying to compute the number of people registered (unless he does a ridiculous number of queries and performs some statistics), or whether someone always using a given nickname as username is registered. – liori May 18 '16 at 12:01
  • Why not require email instead of a username and then even if the given email exists pretend that the account was created (show the usual "you will reach the activation email soon") and then potentially send a warning email to the address saying that someone tried to (re)create their account? – Maurycy May 18 '16 at 17:35
  • @MarkHenderson, to confirm what psmears says, I'm talking about the first time a user proposes a username, it gets locked in or rejected at this point. There would be no chance for it to get saved into your browser's password manager until that point, and if it needs to be changed after that, it should take intervention with the help of the company's customer service representatives. – Ghedipunk May 19 '16 at 17:24
  • If you're worried about your special snowflake username _after_ you've confirmed your email address, and concerned enough to make it easy for attackers to enumerate your account, then take it up with the person who registered your username before you had a chance to, and if you're willing to ragequit, then bye. – Ghedipunk May 19 '16 at 17:24
19

The alternative to allowing multiple users to have the same user name is far worse!

Really what happens with that system

  • User requests a new password
  • You now have to check passwords to make sure no collisions happen
  • The user can use this to brute force other accounts

That's bad. So you need to prevent multiple users from having the same username. You do this by checking.


What happens if a user can find out another users username

This is the scenario you're presented with, and how do you prevent someone from maliciously using it. What risk do you open yourself up to here?

  • A user can find out a username
  • Still doesn't give access, as they now have to guess the password

Okay good, this offers a little more security and still keeps users from having emails and other sensitive information leaked. But now you want to try and mitigate attacks. How do you do that?

Mitigating registration attacks

There are a couple of techniques that exist here to prevent it, but the best by far is rate limiting and maximum attempts that you log by IP.

Rate Limiting, Maximum Attempts, and IP Logging

When a user tries to register, they can only check for available usernames so fast. In all reality it will take a real person a second or two to type in a new username. This lets you do a few simple checks and balances. However this only really happens when they try to register. When a user attacks you and uses a username that isn't registered, all that happens is they have registered that username. Oh well. Now they have to start the registration process all over again, and here is where you can check.

  • User tries to register a lot of accounts really fast: a bot, stop them and kick them out(rate limiting)
  • User tries a LOT: Attacking by hand, kick them out(maximum attempts)
  • User comes back: Same IP address shows a really determined attacker. BAN THEIR IP(maximum repeated attempts)

Now that attacker has exposed themselves and gotten banned at an IP level. You know there is something bad going on here, have a log of usernames that might have been gleamed, and best yet there was no personal information leakage. Great job!

However now you need to go back and deregister all those accounts that were created in an attempt to find other accounts. Good thing you have those logs!


The Downfalls Of Usernames Without Emails

Here's a big flaw that you need to be aware of when you don't tie an account to an email: The username can be preregistered and held for ransom. It is then extremely hard to prove ownership of the username. This let's someone do a different type of attack against your system where they just attempt to pre register a lot of accounts of people and hold them at ransom. Hopefully you have a log from the above checks so you can try and catch them in the act.


Alternatives

You mentioned they have already gone through the email confirmation process, so another way to handle it at this point is to link it to the email instead. After registration, if they want to change their email they'll have to log in, and send a request with a link to their email to change it. This can be done with a time sensitive token. Now they can have a username too to display on the site. The pitfalls are also mitigated here since the display name can overlap and there is no way to try and gain access to another account with that information, but the email provides a check of truth(and possibly some sort of physical identifier like a profile picture)

Robert Mennell
  • 6,978
  • 1
  • 14
  • 38
  • 2
    The point of enumerating usernames is not only to allow brute-forcing, but also to gain intelligence. "Oh, that Targets has an account at SomeSalesServer? Let's search for vulnerabilities!" or "So, @someman is registered at this dating site ...". That's why you try to prevent this beforehand but few pages do a good job preventing this. But good answer ;) – Sebb May 16 '16 at 19:14
  • I'm sorry, the very first sentence of your answer already seems completely out of context. Why are you suddenly talking about checking *passwords* for duplicates? That's not what the question is about. Maybe you can include the bridge of conclusions you've made? – Wildcard May 18 '16 at 03:04
  • 1
    The bridge of conclusions is right there before it. In this system, if you decide not to check for usernames, then there is overlap of users to login, and password can be brute forced from a single username for any account with the same username – Robert Mennell May 18 '16 at 03:06
9

Problem When a user tries to log in, he or she enters their username. When registering, you are told if a username is taken or not. If you find a taken username, you can try to log in with it and possibly hack it.

Solution 1 (which introduces another problem):
If users have to login with an email, knowing if a username is taken won't help. You login with an email, not the username. So, an attacker can't know what to enter in the login form when you know that there is the helloman username. However, now the problem is telling the user if the email is taken.

How to protect with email confirmations
When registering, don't tell the user if the email is taken. Instead, tell them that a confirmation email was sent. Send them an email, and if the email was registered before, send an email saying 1)their account already exists, and 2) that someone tried to register their email. Reassure the user that their account is secure.

If the one who tried to register helloman@example.com is not the real helloman then they probably wont see helloman's email inbox. If the real helloman tried to create a new account with the already-registered email, then they will receive an email saying that they already have an account registered. The main problem with this approach is if a hacker who managed to hack helloman@example.com. However, the most common email systems nowadays are very secure, so it's not to worry. Also, it's not your problem if they have their account hacked.

3

Allowing a user (or an attacker) to find out whether usernames exist or not is known as a "username enumeration" vulnerability. This is summed up well here:

As an attacker if I can use your login or forgotten password page to narrow my list from 10000 targets to 1000 targets, I will.

You can add "sign-up page" to that list. This aids an attacker in any phishing campaign, or any password guessing attack. It is also a privacy issue if Bob's wife Alice can find out if bob@example.com exists on Ashley Madison.com.

Basically if you are allowing users to pick their own username which is separate to their email, then to guard against user enumeration you should allow login with email/password only, and simply use username as a means of identifying with other users on the site.

This way there is no vulnerability in saying Sorry, username foobar is taken, please select another.

This is because unlike email address, these usernames will not be globally unique in the world. Therefore no user privacy is violated when you say one is taken. As an addition, because you cannot login using the username directly, this would not give rise to a user enumeration vulnerability.

As an extra bonus, this also makes account recovery and sign up a more simple process (which is always good for security). That is, there is no need for "forgotten username" functionality as well as "forgotten password" - as they login with their email address, which they will remember, that's the only thing they need to regain access assuming they still have access to their email account. Make sure you are preventing user enumeration on your email addresses though - see this answer.

As an update to this answer I notice that you say you do not want to use email as a username in case the user decides to change their email address. The only reason that I can see this as being a problem if in your architecture you are using "username" as a primary key, therefore any update to the email address (if it were a username) would cause the need for every linked table to be updated. Re-architecture of this to use independent PKs would be the preferred solution to this rather than making username static. (Noted a big assumption here on my part on your reasons for this.)

SilverlightFox
  • 33,698
  • 6
  • 69
  • 185
  • How about saying that if there have been more than a few incorrect-password log-in attempts with a username, the user must supply the correct email or other non-confidential identification information before any further password attempts will be validated; if too many mostly-correct attempts are made, send a notification to the person's email address (counting only mostly-correct attempts would minimize the danger of someone conducting a denial-of-service attack with random bogus login attempts). – supercat May 18 '16 at 16:52
  • @supercat: I can't see the advantage of letting them login with username at all, when email address is much more advantageous. Also, if you're storing passwords correctly (e.g. bcrypt or pbkdf2) then you have no idea if a password guess is "mostly-correct". – SilverlightFox May 18 '16 at 18:08
  • Many people's email addresses aren't exactly confidential, and using email address alone requires that one either allow unlimited password attempts or denial-of-service attacks. If an attacker would have to guess a combo of user name, email, real name, birthdate, etc. before being able to try out more than a few passwords, that would make brute-force attacks much less practical. Passwords obviously should be hashed, but things like real name, etc. generally won't be, so checking whether someone got 3 of 4 answers correct should not be difficult. – supercat May 18 '16 at 18:24
1

There are good answers from @SilverlightFox and @MaxTheBackspace.

I just want to make one thing clear again: It should not be possible by default at any point in the application to let users enumerate a (likely) global unique identifier like an email address. Not even ONCE. Not with IP address monitoring and rate limiting and a CAPTCHA in Klingon language created with finger paint by a toddler.

This is not only about the security of your service but the privacy of the user.

Noir
  • 2,553
  • 13
  • 23
0

It may be a better practice for the login process work with an email and not a username. If you need to have a username by not making it a piece of authentication an attacker can no longer use the username availability check as a malicious tool. At this point the username is just vanity and public facing identity.

I know in a comment you stated you wanted your user to be able to change their email so you didn't want to use this for logging in. It is a completely acceptable practice to do this and allow the changing of the email address. The very site you are on does this the same way.

So now you have the issue of checking to see if someone already registered with an email address. For starters it is much harder for someone to guess someguy54@emaildomain.com over someguy. And then if the attacker knew of someguy on your service and wanted to attack him specifically he would have to know the email he signed up with along with his password opposed to just a password.

But automated attacks and persistent manual attacks do happen. It is very hard to tell if someone tried 50 username combinations is looking for information or just trying to find the perfect username they legitimately want to use. But if someone is trying to sign up using 5 different emails it is a much bigger red flag for abuse.

So by switching the authentication to email over username you get:

  • No public facing authentication information
  • Harder to manually guess information
  • Easier detection of abuse

The downfall is a potential privacy leak like mentioned by Lukas in the comments. If someone wanted to know if a certain user was a member and they knew what email they would likely sign up with they could cross check it with your registration system.

Bacon Brad
  • 3,332
  • 19
  • 26
  • 1
    Not necessarily true. If I want to allow several usernames for the same email, the username system works better, and with emails you run into the exact same problems, only this time you've exposed an email. Now they can check for emails that have been pre registered using the exact same method. The truth is you have to know their email or username already to see if they've registered to check if they've registered. Otherwise there is just an account registered to that email or username. The difference is that username based accounts can be held for ransom, while email based just registers them – Robert Mennell May 16 '16 at 18:35
  • 2
    One thing to consider: I'd rather have my username enumerated than my email address. (From a privacy perspective - think Ashley Madison). – Lukas May 16 '16 at 18:42
  • 1
    e-mails are used as logins mostly for convenience reasons (people remember their e-mail better than 20 different usernames), but they make it easier to guess usernames, especially if you have a large database (from one of the many leaks, say) of them to try. Seperating display name from login name, however, is good advice. – Tom May 17 '16 at 09:16
0

From a user interface / usability perspective, if I've already taken 3 steps (email, phone/ recaptcha), then that reduces the likelyhood of a machine attack, and any further roadblocks will reduce the number of people who want to sign up but give up in frustration. I personally don't mind at all if I try to choose MarkyMark (No, I'm not the rapper), and if some other wanna-be rapper has used that name, I'd far rather have the web site tell me that MarkyMark is already in use (and as others have suggested, also offer the option of allowing them to log in as MarkyMark in case I just forgot that I had already registered 3 years ago), and if the site then suggests I try MarkyMark345 or MarkyMarkyMark -- that doesn't necessarily give away that there are MarkyMark1 thru MarkyMark344 already in use. I appreciate the suggestions. In fact, once a site suggested MarkyMark78 and I tried MarkyMark77 and was successful.

Mark Stewart
  • 159
  • 1
  • 2
  • 15
  • Without some kind of further captcha-upon-attempt-threshold, though, OP's still exposed to username harvesting at this point, right? – Mathieu K. May 17 '16 at 17:09
  • Probably; but if someone is that determined/patient, they will try anything; so like anything, it is a trade-off between annoying customers vs. thwarting crackers. – Mark Stewart May 17 '16 at 17:54
  • 1
    Allow me to rephrase: if I have a burner email address and a burner phone, and provided OP doesn't have some kind of further captcha-upon-attempt-threshold, I can now harvest his entire username list with 5 minutes of proving I'm a human, and whatever time it takes to set up a machine to do the rest. – Mathieu K. May 17 '16 at 18:03
0

Enumerating usernames is a threat because the combination of username + password will give an attacker access to the account/system.

How much of a threat that is depends on your system. For example, in a forum where the username is anyway attached to every post, that threat is very low. If you enforce strong passwords, it reduces the threat.

In your scenario, due to all the data you checked before he comes to that step, enumeration would be incredibly costly, so stop fucking your brain and just go ahead and tell him "username taken", with alternatives offered as suggested by Mark Stewart.

Tom
  • 10,201
  • 19
  • 51
0

This isn't generally a serious concern unless your usernames are email address, which would permit the spamming or phishing of your users. (Or also potentially if you provide a messaging API.)

That said, if you feel like the underlying username needs to be a secret: Let new users choose a display name, but don't allow them to choose their underlying username. Instead, either offer OAuth or append a random number or word to their display name to use when signing in.

And even then, if you do have a messaging API, the more clear-cut protective measure is to ensure that messages are clearly marked with the originating username.

svidgen
  • 711
  • 5
  • 13
-4

Well, the first thing I have to say is what I say to just about anyone asking username/password questions: stop that. Use OpenID and get out of the username/password business.

Secondly, usernames should be looked at as a hash collision mitigation, not as security -- i.e, two people with the same password. From a security POV, you should consider the username as one of the elements an attacker already knows, not as additional security.

So, I would suggest allowing email and possibly telephone numbers as well as usernames to identify an account where they either can't or won't use OpenID (although really, I'd force it's use by setting up my own internal provider as as a second app).

Finally, when you do have to take a username, rate limit the submissions. Take a minimum of two seconds to return the answer and double the time for each attempt, require going through the whole process again if they abandon the session without picking a username.

jmoreno
  • 496
  • 2
  • 9