views:

267

answers:

3

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.

+5  A: 

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.

Alex Martelli
Ah you beat me to it ;) . Just make sure to use the data depending on the users Time zone :)
Ravi Vyas
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.
Longpoke
@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!
Alex Martelli
Haha I didn't even think of that, my way is generalized though! :)
Longpoke
Very clever! Thanks.
jpatokal
+1  A: 

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.

drawnonward
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 :)
Longpoke
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.
drawnonward
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!
jpatokal
I was imagining a read only snapshot of the database not full write access.
drawnonward
A: 

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.

Longpoke
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.
jpatokal
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.
Longpoke
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.
jpatokal
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?
Longpoke