32

We have a system where if you forgot your password and want to reset it, to go to the forgot password page and enter your email address. A temporary link will be sent to your email to reset your password.

Now, when we subjected our app to penetration testing, an issue was found:

Application is giving clues of possible valid email addresses when attempting to reset password. This functionality can be abused by simply guessing possible email address and being able to find valid ones through the error messages.

Well, there's only one field and of course its obvious that if a reset password attempt fails, it's due to an invalid email. Seems this penetration test is wrong. Are there any solutions to fix this issue besides adding an additional field (besides email) for password reset?

Anders
  • 64,406
  • 24
  • 178
  • 215
davshowhan449
  • 361
  • 3
  • 4
  • 13
    it's important to clarify the difference between "invalid email" and "unregistered email" here. if the user types something that doesn't match the format of a valid email address, of course it's okay from a security perspective to inform them that the process failed. if they type a valid email that isn't associated with a registered user, however, that's information to be kept private. – Woodrow Barlow Mar 20 '20 at 15:26
  • What response do you give a user that uses an incorrect email address at login? – DocRoot Mar 22 '20 at 00:37
  • 1
    @DocRoot: It is a best practice to respond with a generic message such as "Invalid email or password" (assuming that you use email + password combination as credentials). – bhorkarg Mar 25 '20 at 21:07
  • @bhorkarg Yes, I know. That was intended as a clue/rhetorical question for the OP. If they are providing a "generic" (error) message at a failed login attempt (as I would expect, since it's a very well recognised practise in interests of user-account security) then why provide a more "revealing" response at another stage in the user-account process? – DocRoot Mar 25 '20 at 23:06

6 Answers6

101

Don't indicate that the attempt "failed". A user (legitimate or otherwise), asks for a password reset link, and gives you an email address. All you should say here is along the lines of

Your submissions has been received. If we have an account matching your email address, you will receive an email with a link to reset your password.

The user will still get the link (success), but attackers won't know whether a provided email address is associated with an account.

yoozer8
  • 810
  • 2
  • 7
  • 17
  • 1
    Why even add the second sentence? Just give a generic "Your submission has been received and will be processed." –  Mar 19 '20 at 22:38
  • 67
    Legitimate users sometimes forget which email address they used, so it's nice to give them a heads up that the request could fail due to a non-existent account vs. the email getting marked as spam, etc. – panofsteel Mar 19 '20 at 22:59
  • 21
    Plus it just lets the user know what to expect. Yeah, most of us have been through the process before and know that we should look for an email. But for someone who is resetting a password for the first time, you want to let them know what to expect to happen next. – Elezar Mar 19 '20 at 23:06
  • 40
    But the same thing can be found out via trying out registration. Unless you are saying "registration may or may not be successful" as well, which sounds terrible to me. All in all, it feels like easily bypassed obfuscation at the cost of usability (if a user mistypes his email, he will never find it out and wait in vain). – Alice Mar 20 '20 at 03:46
  • what if there are pages that come after? like terms and conditions, notices, FYIs, ads, surveys etc. before the actual sending of email? this could affect user experience. A legit user could say, I went all through that and at the end they didnt tell me my email was invalid? – davshowhan449 Mar 20 '20 at 08:12
  • 14
    Are these pages really necessary though? If I had to complete a survey, dismiss several ads and read some ToCs before being able to reset my password, I'd be seriously considering if I use that service at all. Resetting your password should be as simple as writing down my email address and clicking a button. – Christoph Mar 20 '20 at 08:28
  • 14
    -1 Because you talk about the easiest implementation without even touching upon the related threat model. For example as pointed out in the comments exposing already registered user emails is a typical feature of registration forms, and the important thing is often to prevent brute forcing by rate limiting per IP. Nor do you touch upon the huge hit to UX and the dangers of giving the same response (real users being hurt more than malicious actors) – David Mulder Mar 20 '20 at 08:38
  • 1
    @davshowhan449 Users should have to accept any ToC when signing up, not during use – yoozer8 Mar 20 '20 at 11:03
  • 1
    @Christoph "I'd be seriously considering if I use that service at all" is an odd way to write "I'd delete my account and never go back" – user253751 Mar 20 '20 at 12:55
  • 3
    For account registration, you can avoid revealing that information if you make new accounts inaccessible without e-mail verification. None of these techniques are difficult; it's just a matter of deciding whether account existence is secret enough to justify a hit to UX. And of course, you can mitigate the UX issues by sending an e-mail even on failures (e.g., you can send an "account not found" e-mail when a password reset fails). – Brian Mar 20 '20 at 13:09
  • An email should be sent to the stated email address in any case. If there is no account associated with that address, let the owner of that email account know that there has been a password reset attempt. If a third party attempted to reset the password, they know that someone is attacking them. If they forgot which email address they used for registering, they won't wait any longer for the email or think your system is broken but just try their next address. – UTF-8 Mar 20 '20 at 15:07
  • 2
    @UTF-8 Do you mean an email should be sent to the address regardless of whether it's registered in the system or not? That seems questionable. I definitely wouldn't want to get emails from some service I have nothing to do with just because someone plugged my email address into a web form. – David Z Mar 20 '20 at 20:32
  • 1
    @DavidZ So let's just check how popular services we all use handle this situation. Idk, maybe SE? So I just plugged an email address not associated with my StackExchange account into the "I forgot my password" form. What did I get? An email stating: "We received an account recovery request on Meta Stack Exchange for [my secondary email address], but that email does not exist in our records." – UTF-8 Mar 20 '20 at 22:16
  • 1
    @DavidMulder My concern with rate limiting per IP is it often locks out legit VPN users, also a huge hit to UX. – Schwern Mar 21 '20 at 20:04
  • @Schwern Having a small fraction of users who will have a slightly worse, but still functional registration form (given it was programmed well) doesn't seem like a 'huge hit' (especially compared to the alternative: a disjoint registration process where you receive an email to continue registering). But yeah, you're absolutely right that considering VPN users is very much worth it. – David Mulder Mar 21 '20 at 20:08
  • @DavidMulder It's not a huge hit until you're the VPN user that gets locked out. I don't know if you've looked at the news lately, but we're about to see a lot more people working from home on VPNs. :grimace: – Schwern Mar 21 '20 at 20:15
32

The reason for this finding is that a user gets different responses, depending on the existence of the email address. This can be as simple as telling them that the address is not known, or something more subtle in the response.

The easiest implementation to avoid this kind of problem is giving back the exact same response, no matter whether the email address exists or not. A simple

The information to reset the password has been sent to the provided email address. If you do not receive an email, please check that you have supplied to correct address.

would do the trick.

If the email address is known to you, send the password reset information. If it doesn’t exist, don’t do anything. A real user will get the link, while an attacker cannot determine whether an email was sent or not.

TRiG
  • 609
  • 5
  • 14
Demento
  • 7,249
  • 5
  • 36
  • 45
  • makes total sense. thank you. why didnt I think of that before? – davshowhan449 Mar 19 '20 at 13:43
  • 4
    @davshowhan449 - Don't worry, this is a very common vulnerability in web-applications and easily missed. Bad news is, that such issues are actually exploited in the wild and hard to mitigate on short notice, when it happens. Therefore, I recommend fixing it in time, although it is "just an information disclosure" on first glance. – Demento Mar 19 '20 at 13:47
  • what if there are pages that come after? like terms and conditions, notices, FYIs, ads, surveys etc. before the actual sending of email? this could affect user experience. A legit user could say, I went all through that and at the end they didnt tell me my email was invalid? – davshowhan449 Mar 20 '20 at 08:13
  • 23
    @davshowhan449 Then get rid of those additional pages, too. And good riddance, because that sounds like an awful experience in the first place. But maybe that is worth a new question. – Graipher Mar 20 '20 at 09:49
  • 10
    @davshowhan449 I don't think I've ever seen that in a password reset page. You're presumed to already be a member of the service, there's no need to ask the user to agree to T&C. Other stuff doesn't show up until you respond to the link that's emailed. – Barmar Mar 20 '20 at 15:26
  • 1
    @davshowhan449 Although it's still not a great user experience, one way to fix that workflow would be to move any *necessary* pages to after the user clicks the link in their email. – GalacticCowboy Mar 20 '20 at 21:39
21

What are we protecting against?

First of all one should ask what they are protecting against exactly. In this case there are two different threats:

  • Threat 1 An attacker brute forces random emails to find valid registered emails. This could be theoretically used to create spam lists, but as far as I know has never been done as it's simpler to just sent the emails than go through the extra trouble (The number of time that I have receive spam mail claiming my [some US bank] account needed action despite not living in the US is countless).
  • Threat 2 An attacker is targeting a specific user or a list of specific users. This is especially important when the bare fact someone is registered somewhere can have consequences. Example: The very act of having an account on Grindr or Ashley Madison can be dangerous in certain communities.

The bigger picture

The next question to think about is what other places might expose the same information and considering those as a whole. Typically the registration form will inform the user if the entered email is already registered, but this of course doesn't apply to most B2B software. Beyond that 'share with user' features (an input box where an email can be entered to share some object with this user) will often also expose this information, but as those are rare I won't include those in this answer.

Solutions for a system with public registration

First of all it's good to acknowledge to not informing the user that an account already exists during the registration process is from an UX perspective very unpleasant. The same applies to password reset forms that silently fail1 when the user makes a typo or has multiple emails. This doesn't mean it's something one shouldn't do, but one should weigh it against the security risks and benefits.

Given a system with public registration the important thing to consider is how big Threat 2 is to the users of your product. Given even a modest risk it can be prudent to change the registration form to start with only entering a name and email, giving a message "check your email to continue sign up", and if the email is already registered sending the user an email informing them of this. Similarly in that case the password reset form will not inform the user in any way whether the email is valid.

On the other hand if Threat 2 is minimal we have to model exclusively against Threat 1. Of course the previous approach will also function perfectly against Threat 1 exclusively, but considering the UX cost it's worth considering other solutions. The most obvious solution is rate limiting2 on both the 'email exists check' and 'password forgotten' checks (technically those calls can even go to the same API endpoint). These will often be designed to be fairly lenient for the first 10 calls or so, but get very limited very quickly after that point.

Important: Never remove error messages from the password registration without also implementing similar restrictions on the registration form.

Solutions for a system without public registration

It all boils down to the same issues as above (and I should have written this first), but without the 'extra' cost of the hit to the registration UX it's relatively 'cheaper' to have a secure password forgotten form, although of course it's still unpleasant for users and you still can consider whether rate limiting isn't enough for your specific threat model.

Notes

1: Instead of silently failing it's wise to at least sent an email to the entered email informing them that their email wasn't found. This 1) demotivates attackers from mass abusing the system (as it will be noticed) and 2) prevents users from wondering why they aren't getting an email.

2: Do note that rate limiting can negatively affect users of large networks or VPNs. Always consider how important that audience is to you and based on that spend an adequate amount of time to ensure the application stays functional even when rate limiting is harshest (e.g. by lowering the rate limit by solving a captcha or setting the maximum rate limit to around once per minute and ensuring the application will wait the full minute (note: still will be unpleasant given a team of users signing up for the same service at the start of the same meeting)).

David Mulder
  • 1,349
  • 1
  • 8
  • 16
  • 1
    Wouldn't it be also ok like this: "An e-mail has been sent to you for continuing signup, if you are already registered you will receive no e-mail." This would prevent an attacker to spam a for him known e-mail adress. – SAJW Mar 20 '20 at 19:45
  • 1
    @SAJW I think what you're describing is the same as I described when I wrote "Given even a modest risk it can be prudent to change the registration form to start with only entering a name and email, giving a message "check your email to continue sign up"". But if you have any idea how to make it clearer or better: feel free to edit – David Mulder Mar 21 '20 at 08:21
  • Ah, never mind. Your way is sufficient enough because if an attacker knows your e-mail, he can spam you if you are NOT registered, so we don't gain much with my solution. – SAJW Mar 21 '20 at 20:37
  • @SAJW Somehow I completely misread your comment and now it's crystal clear... . But yeah, in general silently failing is super dangerous, because the user never knows whether some slow mail server or spam filter caught his email, or whether he actually is supposed to not receive an email. – David Mulder Mar 21 '20 at 20:44
20

As an addition to the answers above (Would be better suited as a comment but can't do that yet) another step the hacker can take is to measure the TIME of your response to the form. It might take you 10ms to determine the email doesn't exists, while it takes you 100ms to generate a reset link and send an email. The hacker can know if the response is slower that its a successful find. You can employ a random sleep timer in cases where the email is not found so that both responses take the same amount of time.

Michael P
  • 311
  • 1
  • 3
  • 14
    Welcome to the site, have your first +1! Thanks for your addition. There is an even easier solution to the problem though. Just send the response right away, without checking the email first. That way the response time is independent from the validity of the email address as well. – Demento Mar 19 '20 at 15:10
  • 8
    @Demento That's conceptually easy but may be technically difficult - a lot of basic web backends don't make such asynchronous processing easy, because by default they tie the lifetime of the processing thread to the HTTP response. – Bob Mar 20 '20 at 00:19
  • 5
    @Bob yes it is a bit more difficult, but also still very tractable. If you have a message queue, you can use that, or go poor man / old skool with a cron that runs every minute popping items off a database of pending reset submissions. – Kroltan Mar 20 '20 at 02:22
  • 3
    This problem goes away if you send an "unable to reset password: account not found" e-mail. – Brian Mar 20 '20 at 13:11
  • 1
    Brian I upvoted you, but note that (without controls) you have given Attacker A the ability to spam user S using your service. – J. Chris Compton Mar 20 '20 at 13:37
  • 1
    Just a side note here - don't use a random sleep, it can be statistically filtered out. See e.g. https://stackoverflow.com/questions/28395665/could-a-random-sleep-prevent-timing-attacks A delay calculated to be long enough to make both responses take the same time may help, but random will not. – Harun Mar 21 '20 at 03:29
  • If you send the same response to everyone, as suggested in the other answers, the HTTP response can be very simple, even a static HTML page or redirect. The check and send is either done asynchronously, or if your framework does not support that after the response has been sent. That is instead of check, send, respond it's respond, check, send. – Schwern Mar 21 '20 at 20:11
9

You should weigh up the problem versus the inconvenience of solution. It's often a tradeoff.

In my opinion it is usually better to sacrifice this bit of security for the sake of user experience unless someone might get persecuted for being registered in your site.

From time to time I come years later to a site and try to find which email did I use for the particular site. The sites that hide this information are quite annoying.

Džuris
  • 269
  • 1
  • 7
  • 2
    A great addition to the other answers. Every security feature should be evaluated for the current context to decide if it's costs are worthwhile. The answer isn't always "yes". – Conor Mancone Mar 20 '20 at 03:47
  • 1
    In the case where I don't know which email I used, I'll often search for the site name through my main email addresses to see which one I registered with. Especially useful inconjunction with the google feature of being able to add +anything to your email address. – stan Mar 20 '20 at 06:50
  • 1
    It's not just about persecution, but also about phishing. Imagine someone gets a "password reset" mail and 5 Minutes later he received a mail apparently from your website saying "We recognized a login to/purchase from your account that might not be from you. please go to www.thisisascam.com to check whether your account was compromised". Lots of people will log in to the scam website, because the reset email primed them for it and the scammer knows that the person has an account with you, so he can format the message differently than in a generic spam mail. – Morfildur Mar 20 '20 at 08:29
  • 1
    @Morfildur: Most phishers don't bother checking account existence before sending their e-mails. That's why you get phishing e-mails for services you don't use. – Brian Mar 20 '20 at 13:13
1

Note that this also (besides the other answers) applies to the registration screen, instead of complaining about already registered user sent them an email that they are already registered and provide no feedback about this in the web app besides "check your mail".

This is best done if you ask for minimum input on the first step before the email validation (which is also good for GDPR, where you can prove that the user got your info email and continued to register).

In fact it's no hassle on registration and should always be done. For reset password function we provide an option in the software where you can select direct feedback or email feedback. (In corporate scenarios, especially on the Intranet guessing emails is not a real threat).

yoozer8
  • 810
  • 2
  • 7
  • 17
eckes
  • 962
  • 8
  • 19