Password reset by email without a database table
Asked Answered
K

5

7

The normal flow for resetting a user's password by mail is this:

  1. Generate a random string and store it in a database table
  2. Email string to user
  3. User clicks on link containing string
  4. String is validated against database; if it matches, user's pw is reset

However, maintaining a table and expiring old strings etc seems like a bit of an unnecessary hassle. Are there any obvious flaws in this alternative approach?

  1. Generate a MD5 hash of the user's existing password
  2. Email hash string to user
  3. User clicks on link containing string
  4. String is validated by hashing existing pw again; if it matches, user's pw is reset

Note that the user's password is already stored in a hashed and salted form, and I'm just hashing it once more to get a unique but repeatable string.

And yes, there is one obvious "flaw": the reset link thus generated will not expire until the user changes their password (clicks the link). I don't really see why this would be a problem though -- if the mailbox is compromised, the user is screwed anyway. And there's no risk of reuse, since once the user's password is changed, the reset link will no longer match.

Kareem answered 3/5, 2010 at 1:15 Comment(0)
C
8

To remedy the obvious flaw, add the current date (and more time-related info representing current fraction of a day if even a day is too long) to what you're hashing to generate the mystery string and check it -- this makes the string "expire" (you may check the previous as well as current date or fraction if you want longer "expiry"). So it seems to me that your scheme is quite viable.

Calamitous answered 3/5, 2010 at 1:22 Comment(4)
Ah you beat me to it ;) . Just make sure to use the data depending on the users Time zone :)Ghirlandaio
How do you derive the date of expiry from the user submitted hash to check if the current date is past it? It would have to be encrypted and tacked on. Otherwise: A) You just encode it, user breaks encoding, defeats the purpose of even having a time check. B) You leave it as plaintext, same thing. Then again if you have some global secret S to avoid space waste, you could send the user HASH(S || time || userpass), time; The user sends back hash, time, which would allow the server to test HASH(S || time_user_sent_back || userpass) against the hash that the user sent back.Nest
@Longpoke, if (for example) the string's supposed to be valid on 2010/05/06 and 2010/05/07, you append 2010/05/06 to the string you're hashing -- that's it. On checking, you check the hashes you get by appending ISO formats of today and yesterday -- that's it, hardly rocket science. If you want better granularity than a day for special-string expiration purposes, then you also use the appropriate fraction of a day -- e.g. if you want the string to last about 1.5 days, use "half days" (e.g. AM or PM) among what you're hashing (and check 3 possibilities) -- also not hard!Calamitous
Haha I didn't even think of that, my way is generalized though! :)Nest
P
3

If someone accessed your database with password hashes, they would not know the actual passwords. If you implement this system, then they can generate the reset links and reset the passwords themselves. With random strings and knowledge of a compromise, you can invalidate all the random strings, and only users in the process of resetting the password would be compromised even without knowledge of the access. Not a likely scenario, but it might be worth considering given the nominal overhead of random strings.

Plummet answered 3/5, 2010 at 2:40 Comment(4)
Once your site is compromised, you want to reset all the passwords too, because the attacker may have logged them in plaintext as they are submitted to the server (a non-avoidable case; only SSL client certificates can stop this). Not to mention the attacker probably broke a good 90% of the hashed passwords within a few hours. Plus, if you use Alex Martelli's advice, you can add an additional check on the reset request against the time of compromise, which is effectively the same as what you just suggested :)Nest
Except that not every user will have a random string but every user effectively has a generated string, so if you are not immediately aware of the compromise only a few users are affected anyway. Besides the 90% with weak passwords, of course.Plummet
Gah! That's definitely a real vulnerability, although as Longpoke says, once the attacker gets in the DB all bets are off: they don't need to futz about with reset links, they can just change the passwords directly in the user table. +1 for finding it though!Kareem
I was imagining a read only snapshot of the database not full write access.Plummet
M
3

Actually after thinking about this again, your method is potentially less secure than "The normal flow".

If you just send back HASH(HASH(user's original password)), I can see scenarios where this can give an attacker leverage:

Scenario 1:

  1. Jim registers on your site as [email protected].
  2. Jim requests a password reset, but doesn't use it. The reset email is left sitting in his inbox for eternity.
  3. Jim changes his email address on your site.
  4. [email protected] is compromised by Bob.
  5. Bob now runs a bruteforce attack via his distributed GPGPU farm and recovers Jim's password.

Scenario 2:

  1. Jim uses a the password jimjonesupinthisma! for his banking account.
  2. Jim registers on your site as [email protected]. [email protected] is not in any way associated with Jims bank account.
  3. [email protected] is compromised by Bob.
  4. Bob now requests a reset, he now has HASH(HASH(jim's password)).
  5. Bob now runs a bruteforce attack via his distributed GPGPU farm and recovers Jim's password, which he then uses to access Jims bank account.

Scenario 3:

(Your site uses TLS, users register via TLS.)

  1. Jim registers on your site as [email protected].
  2. Bob requests a password reset on Jims account.
  3. Bob works for NSA at Room 641A.
  4. Bob uses his global internet sniffer and obtains HASH(HASH(jim's password)) as it's emailed in plaintext to [email protected].
  5. Bob now runs a bruteforce attack via his distributed GPGPU farm and recovers Jim's password.

Variants of scenarios 1 and 2 happen all the time (depending on how strong the hash and password are), I'm not so sure about 3. The point is, your method leeks unnecessary information, which can indeed leverage an attacker against your user.

I suggest you use randomly generated tokens that have nothing to do with the user's password.

Moore answered 3/5, 2010 at 3:36 Comment(4)
Sorry, I'm going to disagree here: all your scenarios seem to assume that the twice salted and hashed string is of any use in recovering the original string, but this just doesn't seem to be the case. MD5 is known to be vulnerable to collision attacks, but your hypothetical hacker would need a successful preimage attack to get the password back from the hash. The best such attack known to date against a single round of unsalted MD5 has a complexity of 2^123, and cracking even that is firmly in the realm of science fiction.Kareem
You don't need (and usually don't want) a collision, just a password (via dictionary and then fallback to bruteforce of a charset through 1-n digits). Most of users have weak passwords, and no hash is really gonna save them. These attacks are real, although not as common as the typical script kiddie attack you hear of. Actually it's more to do with determination and being at the right place at the right time.Nest
These attacks are real against unsalted MD5, where you can use a rainbow table to reverse the hashes, but the passwords here are salted and double-hashed.Kareem
What? Stronger hashing only slows it down. Preimage is potentially defeated by salt, yes, but you can't really reduce the bruteforce time that much without killing your server's performance. If you are so confident that your hash is magically strong, why don't you publish a list of users and their corresponding hashes?Nest
K
0

let's say on a very rare case, two of your users had the same hashed password even after concatenating random salt to it; there would be a problem right? I guess it wouldn't hurt if you add email or Hashid of user_id to the reset password link.

Kleeman answered 28/6, 2016 at 14:2 Comment(0)
M
0

Just passed by this issue and have an idea:

1) Issue a JWT (Json Web Token) with info about user account. The token has an expiration, say 1 hour.

2) Send the token to the user in an email/link

3) User clicks the link, the token is sent to the server endpoint, the token is validated. If valid, you unpack the token and updates the user account

Any flaws in this approach? No database is touched (except for new user passwords if necessary)

Mozellemozes answered 13/2, 2017 at 9:15 Comment(2)
Yes, you can use the token multiple times, but I*m ok with that. It's a good thing. I set the token expiration to 1 hour, so misuse is very unlikely imho. What am I missing? It's so simple and works beautifully, that I must have missed something.Mozellemozes
Also, I would put the ip address of the request into the token. Then, when validating, match the ip of the caller. The token can then only be used from same device that initiated request. This is just extra measure. IP spoofers would still need the token, and within 1 hour.Mozellemozes

© 2022 - 2024 — McMap. All rights reserved.