In a previous blog post, I discussed several pitfalls of information disclosure during registration and authentication and how difficult it is to prevent them. In this article, I’m going to attempt to show you that these same vulnerabilities can exist in an application that utilizes WebAuthn. I will attempt to show that you can, in theory, eliminate them with ease. However, I will also show you that given current browser support, eliminating user enumeration is impossible.
If you aren’t sure what WebAuthn is, I invite you to read about its justifications and a general overview of the protocol. In short, WebAuthn is a powerful protocol that allows web developers to harness the power of multi-factor authentication and forgo the use of all passwords. This is a great thing, because the overwhelming number of data breaches are a result of those pesky things.
Registering Credentials with WebAuthn
Just as in password-based systems, with WebAuthn the web application must be able to identify users with a unique identifier. WebAuthn refers to this unique identifier as the user handle. An authenticator (a device which authenticates the user) ultimately ties this handle to a credential. Further, the authenticator scopes credentials and user handles to their respective domains. This means that two different web applications operating on different domains don’t have to worry about user handle collisions. They can choose any user handle they desire without the risk of affecting the other domain.
Let’s take a look at an example. Note that in this example, I’ve removed several superfluous fields to simplify things.
- Web Application A is accessible via https://www.web-application-a.com.
- Web Application B is accessible via https://www.web-application-b.com.
The user utilizes a roaming authenticator. The authenticator’s table looks like (in a very trivial sense):
The user registers with Web Application A. Web Application A dishes out a user handle “user-01”. Again, keep in mind that I’m simplifying things here. In the real world, this user handle would be a random number upwards of 64 bytes long. Web Application A registers this handle with the user’s roaming authenticator, whose table now looks like:
The user registers with Web Application B. B dishes out the same handle “user-01” and registers it with the same authenticator:
In this overly simplistic scenario, an attacker cannot enumerate users, because the web application generates the user handle. The risk of user enumeration does not begin to rear its ugly head until we begin to consider the different authentication use cases that WebAuthn is attempting to fulfill.
Authenticating a User With WebAuthn
Utilizing the example data that we established in the Registration section above, let’s assume that the same user has a secondary account registered on Web Application B. The application hands out a user handle “user-02” and registers it with the same roaming authenticator. Here’s what the authenticator’s table looks like now:
In this case, when a user wishes to log into Web Application A, the web application will kick off an authentication process. Because the user has a single handle tied to A’s domain, the authenticator’s selection is simple – choose user-01. As a matter of fact, in this case, the authenticator performs this selection process is silently, without user intervention.
A Vomitous User Experience
However, what if the user wishes to log into Web Application B? There are two options here. The authenticator must present these options to the user. The big question is, “Which is which?” Don’t forget that the user handles are binary arrays. If this make believe user interface doesn’t make you gag, I’m not quite sure what will:
Keep in mind that there are only two options here. Can you imagine what it would be like for a family of 4 or 5 sharing the same authenticator? If everyone has an account on the same website, there’s going to be some trial and error involved in selecting the right credentials.
Adding Context to Credentials
The creators of the WebAuthn specification realized that user’s were going to need some contextual information in order to select the appropriate account. Thus, they have given the user three additional fields to track personal information:
- Name: This is essentially a human-readable username. The user handle is a binary field up to 64 bytes long, so you can suspect that users would be highly allergic to its appearance.
- Display Name: This is a human-readable name to specifically designed to display in the authenticator’s user interface.
- Icon: This is the URL of an avatar, or other image that the user can use to identify his/her account.
Let’s introduce a few more accounts into the authenticator and augment the table with this additional information:
Now, when a member of the family of four attempts to login to Web Application B, the authenticator can provide much more contextual information to allow a user to select the appropriate credential. Mom chooses the flower. Dad chooses the walking cane.
This is a much better user experience and poor Little Johnny didn’t go into anaphylactic shock.
If Tony Hoare’s introduction of Null references is known as “The Billion Dollar Mistake,” then Fernando Corbato’s introduction of passwords into MIT’s CTSS system is probably a trillion dollar one.
As developers, our industry has conditioned us since the 1960’s to protect information by prompting the user for a username and password. Of course, with WebAuthn, there is no need to prompt for a password. But what about a username?
If we follow the same path as we have for years, then when the user attempts to register with a username that is already in existence, it ends with failure and information disclosure. The problem is more pronounced when a user attempts to register with an email address as a username. Why? Because email addresses are more predictable and are more tightly tied to our identities.
Harvesting Users During Registration
An attacker can harvest usernames during the registration ceremony using the following methodology:
- Attacker navigates to the web application’s registration screen.
- Attacker enters a username and attempts to register.
- The web application backend searches for a user account with that username.
- If a user account is found, an error message is returned.
- Otherwise, continue with the WebAuthn registration process.
- If the attacker receives the error message, the username is harvested.
- Go back to step 1 to harvest the next one.
Harvesting Users During Authentication
An attacker can also harvest usernames during the authentication ceremony using the following methodology:
- Attacker navigates to the web application’s login screen.
- Attacker enters a username and attempts to login.
- The web application backend searches for a user account with that username.
- If a user account is not found, an error message is returned.
- Otherwise, provide the associated user account’s user handle to the authenticator in order to continue with the WebAuthn authentication process.
- If the attacker receives the user handle, the username is harvested.
- Go back to step 1 to harvest the next one.
As you can see in the above examples, user enumeration can still plague an application using WebAuthn.
A Less Than Ideal Solution
The WebAuthn group recognized that attackers could harvest usernames during both the registration and the authentication ceremonies. In Section 14.10 of the specification, the problem is outlined in detail with several mitigations. In this section, I intend to show that these mitigations are ineffective.
It’s All About Privacy
First of all, let me discuss what I believe the WebAuthn group got correct. I firmly believe that they have correctly categorized username enumeration as a privacy consideration rather than a security vulnerability. In password-based systems, leakage of usernames can be quite devastating, because there is a high probability that the same password can be used by the same user across multiple websites. With WebAuthn, this will never happen. Each credential is scoped to a domain and each login gets its own credential. There is absolutely no duplication across different websites. Thus, if a single website were compromised there is no risk of cross-compromise.
It is still a very large privacy consideration, though. I have blogged about this in detail, so I won’t rehash it here. Suffice it to say that if the username is in any ways tied to a user’s real identity, this breach in privacy could pose a real threat to the user.
The Myth of Username Reuse
The WebAuthn group has chosen to mitigate username enumeration during the registration ceremony by recommending web developers disallow registration of usernames that look like email addresses. The reasoning is, and I quote, “it will be less likely that a user has the same username at this Relying Party as at other Relying Parties.”
Yes, even I, as a security professional, am guilty of using the same username on multiple websites. If someone beats me to the punch, I just use an alternate. I have a bank of about three or four common usernames that I use. Why? Because I don’t have the best memory in the world. And, yes, I still use a password manager on top of it.
So, this is simply a level of information disclosure that I am uncomfortable with, not just as a security professional, but as a privacy-minded citizen.
The WebAuthn’s proposed mitigations for authentication ceremonies aren’t any better, in my opinion. They propose to return an imaginary user handle in the event that a user provides a username that isn’t in use. In my mind, this produces a myriad of issues that I cannot reconcile:
- By simply returning a random user handle for an unregistered username, an attacker will get a different user handle with every login attempt.
- You may try to mitigate this by tying a fake user handle to session state. However, an attacker can bypass this control by requesting a new session.
- Then, you may try to mitigate this by persisting fake user handles on the backend. Unfortunately, this presents the risk of a DoS due to resource exhaustion when the attacker attempts to login using millions of different usernames.
- Next, you may try to mitigate this by automatically scaling out your database. When a user attempts to register with a username for which a fake user handle had been created, you run into one of two issues:
- You must invalidate the fake user handle. In this case, a subsequent attempt to login by the attacker will reveal a brand new user handle, indicating that a user with that username is now in existence.
- You must retain the fake user handle. In this case, the attacker already has the user handle for future use.
- Finally, you beat your head into a brick wall repeatedly until the pain stops.
I really think that we can do better.
A Better Solution
As I mentioned above in the Authentication section, an authenticator uses the name, the display name, and icon to provide contextual information to the user so that s/he can select the appropriate credential. This means that the username, in this sense, is not a means for a web application to uniquely identify a user. That’s the responsibility of the user handle.
Now, I need to state that my opinion is not the same as the WebAuthn group. The specification clearly implies that the username field uniquely identifies a user to the web application. I would argue that there is no reason for this, however. I firmly believe that the web application should track the user based upon the user handle that the backend server created during the registration ceremony. If multiple user’s want to identify themselves to their own authenticators as “big-daddy”, who cares? It is simply contextual information.
But what about authentication? The specification recommends that the developer query a set of user handles via a username. The web application then provides these user handles to the authenticator. The authenticator then filters disallowed credentials out of the user interface. If a single credential remains, then the authenticator signs the user in silently.
This flow looks something like:
According to the specification, this filter of allowable credentials is optional. If the web application does not provide a filter and the authenticator contains multiple credentials, then the authenticator is supposed to allow the user to select an appropriate credential. Therefore, there is no reason to prompt for a username on login. And no username means no threat of user enumeration.
An Unfortunate Roadblock
If something sounds too good to be true, it probably is. In our case, all we need to do is provide the call to navigator.credentials.get() a set of options without a credential filter. And this is where the proverbial manure hits the fan.
Attempting this yields the following error in Chrome or Opera:
Resident credentials or empty ‘allowCredentials’ lists are not supported at this time.
You get the following error in Firefox:
An attempt was made to use an object that is not, or is no longer, usable
In Edge (if anyone actually cares):
This means, out of all browsers that implement WebAuthn, none of them currently support an empty credential filter.
Unfortunately, this leaves developers with a less than ideal solution in the immediate future: require a username and open the application up to user enumeration.
In time, WebAuthn is going to change the world. Given that this is a brand new specification, there are bound to be a number of bumps in the road. Even with the inability to shed the problem of user enumeration, WebAuthn is still light years better than the alternative. There is an awful lot of potential here. It will be exciting to watch as browsers begin to adopt this specification.
Photo by Skitterphoto from Pexels.
Thx, you’re describing the problem really good and easy to understand. However, I miss one quite obvious solution: What about deriving user handles via a hash function from the username? You won’t need to save anything but still get persistance. This will work for the login.
For registration, I’d go the route via additional email confirmation. You’ll need an email (or other fallback) anyways because you need a way to recover the account in case your authenticator fails or you just want to login using a different authenticator (e.g. on a phone).