7

When someone registers on a website with their Email addresses, usually they are asked to verify or confirm their email by clicking on a link in a email address that they are sent to. Same goes for subscribing or unsubscribing to a mailing list.

Usability wise, this is great. It's very quick and easy to do, I can't think of anything that beats it.

I am not sure if I am overthinking things, but I just wanted see if I missed anything or misjudge anything.

So as far as I know the purpose of email verification/confirmation is

  • To make sure that the email address is correct
  • To make sure Emails that are sent to this address can be read and received
  • To make sure the email address really belongs to the person who's trying to sign up

With the current popular implementation of just one link that they click and that verifies the email instantly, someone could just brute force the verification link and circumvent this whole step.

Just visit verify.php?code=YOURBESTGUESSHERE and try all kinds of different codes.

An attacker could now make the following exploitations:

  • hurt my business by unsubscribing a bunch of people from my mailing list
  • add a bunch of people to my mailing list without their consent, since they might not be interested in my content, they could think I'm spam and a bad business
  • someone could farm addresses by checking the responses on the verification page (for example if the response is "mail@server.com is already verified" or so)
  • someone could create fake accounts without the need of having an actual working email address

I am not sure what the benefit of the latter would be, and it seems it would be much easier to just create a throw away address for this purpose, but I just wanted to put it up there to be complete.

My Questions:

  • Did I miss any other purposes or exploitations of email verification / confirmation?
  • Should I add layers of security to the email verification, such as a captcha or time delays to prevent brute forcing?
  • Should I ask for additional information other than the reset code? Like username or email address again? Or a security question type thing, or another piece of information they entered when registering?

What are best practices for this whole thing, and how much worry and effort does really need to be put towards it? Risks vs. Benefits / Security vs. Usability... ?

oliver_siegel
  • 1,666
  • 3
  • 22
  • 37
  • 4
    Maybe you should ask this as a specific question on http://security.stackexchange.com? – SilverlightFox Mar 02 '14 at 10:58
  • As of writing this, sending a link to user's email is unsafe (can result to impersonation), especially if your users are likely to use either Gmail for email or Chrome for the browser (Chrome, Chromium, Microsft Edge, Brave Browser, DuckDuckGo Browser are all using chrome engine). Prefer to send a code to the user email instead, and if you must send a link, make sure you have a dedicated page to handle confirmation page that requires user action (like a click) or requires JavaScript to run and send the code to your server. Read more: https://stackoverflow.com/a/63427303/3563013 – McKabue Aug 17 '20 at 12:41

2 Answers2

9

If the attacker's search space for YOURBESTGUESSHERE is large enough, brute force becomes infeasible. Use {a code derived from {email address plus timestamp} (which may have arbitrary other stuff, such as a random nonce, incorporated)} fed through a known-good implementation of a known-good one-way hash function.

Ensure the code is only good for a short time (a couple of days, perhaps) after it's used.

Don't leak information when the code is presented - the real user of the code knows what email address it applies to, and nobody else needs to.

mlp
  • 809
  • 7
  • 21
  • Is there any advantage to giving the code an expiration? I understand why this is necessary for a reset password code, but for email confirmation, I can't think of a situation where a non-expiring code would be an issue. – Josh Noe May 06 '17 at 21:57
  • @JoshNoe, expiring magic numbers allows you to purge them from your database, avoiding another potential avenue for DoS. – mlp May 15 '17 at 03:12
  • @mlp that's the one thing i'm struggling to wrap my head around: how do you put a time limit on the codes and then purge them from the database?! help please – oldboy Mar 21 '18 at 15:57
  • 1
    @Anthony, when you generate the magic number, insert it and its time-of-expiry into the database. When a code is presented, if it exists in the database then allow access (and optionally remove that code from the db). Regularly (hourly/daily/weekly/whatever) run a query that removes any entry whose time-of-expiry is "before now". – mlp Mar 23 '18 at 20:16
  • @mlp so i had figured it out. what i'm doing is using 2 tables, one for unverified and one for verified. once the user activates their account by clicking on the link, i transfer over the necessary info to the verified table and delete the entry in the unverified table. is there any way to automate the hourly/daily/weekly/etc query? – oldboy Mar 23 '18 at 20:36
  • @Anthony to automate the query is an OS-level thing. Unix-like systems have `cron`; Windows has a "Task Scheduler" - use whichever is appropriate for your server environment. – mlp Mar 26 '18 at 18:10
  • @mlp yeah, that's what i thought. currently i'm just using shared hosting, but i will eventually switch back to my own server, so i won't worry about it until then – oldboy Mar 26 '18 at 18:27
  • The hash of the email address + timestamp _is_ bruteforcable. It only relies on the attacker not knowing this pattern. Trying all possible timestamps for the last few minutes is not that difficult. This can be securely implemented by using a keyed MAC (e.g. HMAC) and keeping the used key secret on the server. – Joris Sep 19 '19 at 07:47
  • @Joris "all possible timestamps" is a very big number if you use a fine-enough granularity timestamp (nanoseconds, anyone?). And "a code derived from" does not exclude tossing a random nonce into the hash. Brute force becomes infeasible. I seem to be repeating myself. – mlp Oct 24 '19 at 21:54
  • @mlp That is still based on the assumption that there is no information about the timestamp when this was generated. If the same database also contains a `created_at` field that has a 1s granularity, you now only have 1M possibilities if you used a 1ns timer for the hash. Sure, if you place a rate-limit in place, this could be good enough. But my main concern is that you add a weakness to the system that does not have to be there. If you use a Secure Random Number Generator in the first place instead of a timestamp, it is not bruteforceable at all. – Joris Oct 27 '19 at 16:28
  • @Joris you mean something like this https://stackoverflow.com/questions/8855687/secure-random-token-in-node-js – PirateApp Jul 10 '20 at 07:48
  • @Joris, if you want to propose a "non-bruteforceable code" that differs from what I wrote, do it in your own answer. Don't fundamentally change what I wrote - I still do not agree with your argument. Your edit 2 days ago (and, your comment 8.5 months ago) do not appear to take into account my own comment of 3 days before that. – mlp Jul 12 '20 at 16:39
2

You can always increase security, if needed. Think of combining the link with a unique code. So when brute force allowed the to find a link, they still need to enter a random code from the email.

Suggestions like number of attempts would be one of the first I would implement, to avoid system issues (number of request) rather then security.

Ralf
  • 253
  • 1
  • 11