NotionCommotion Posted December 19, 2014 Share Posted December 19, 2014 I came across this amazing (not) blog to allow the user to reset their password. It basically does: User submits their email to server and requests new password. Server gets their users_id from the DB based on their email, and emails them with a link which contains ?encrypt=md5(1290*3+USERS_ID). When clicked, server retrieves user where md5(90*13+USERS_ID)=$_GET['encrypt'], and display a form. I think the math is a typo. When the form is submitted, the password is changed. What is the correct way to do this? Quote Link to comment Share on other sites More sharing options...
NotionCommotion Posted December 19, 2014 Author Share Posted December 19, 2014 (edited) I am thinking of the following. Please advise if this is adequate. User submits their email to server and requests new password. Or should they submit their username instead of their email? Server retrieves their users_id from the DB based on their email, and stores the users ID, a strong random value and the datatime in another table. Technically, this is using a GET request to change state, but I don't think there is a way around doing so. Maybe http://php.net/manual/en/function.mcrypt-encrypt.php could be used, but don't think so. Email is sent to user with link containing strong random value. User clicks link, random value is used to pull up users ID and date requested, and if date requested is within 24 hours of now, a form is presented with new password field and confirm new password field and hidden input with random value. Maybe the secret question challenge is also presented (see http://forums.phpfreaks.com/topic/293181-should-secret-questions-be-used-to-allow-password-changes/). When the form is submitted, random number is confirmed and maybe date again, and the password is changed. Edited December 19, 2014 by NotionCommotion Quote Link to comment Share on other sites More sharing options...
Frank_b Posted December 19, 2014 Share Posted December 19, 2014 (edited) Hi, First of all it makes a difference about what kind of users you are talking about. Are these random users from all over the world like on this forum or are these users inside a company? If from around the world and you want them to be self supporting then there are two possibilities: a) a user had forgotten his credentials. b) a user just want to change his password. in situation a it is not possible for the user to login. Therefor there should be a password reset function. As we know we human can fill in any name, address, age and more we want. it can be a fake one, the server does not know. The only sureness you can build in is if a new user register is to check if he is the owner of an emailbox. We can do that by sending the user a link that he have to follow to register himself. a link like this could be https://mydomain.com...nvasperuq84j-y0. The last part of the url is a unique identifier to see if it matches a new user account. Later if this user loses his credentials you can offer to send the emailaddress again. After that you should send the user a same email with a url with a unique identifier. if he clicks that link then the user would be able to change the password for that account. The username could be sended again on request. Edited December 19, 2014 by Frank_b Quote Link to comment Share on other sites More sharing options...
requinix Posted December 19, 2014 Share Posted December 19, 2014 (edited) I came across this amazing (not) blog to allow the user to reset their password. It basically does: User submits their email to server and requests new password. Server gets their users_id from the DB based on their email, and emails them with a link which contains ?encrypt=md5(1290*3+USERS_ID). When clicked, server retrieves user where md5(90*13+USERS_ID)=$_GET['encrypt'], and display a form. I think the math is a typo. When the form is submitted, the password is changed. What is the correct way to do this? Depends mostly on step 3, but let's just say "no": I try the system myself and get some random-looking hash value. I see that's MD5 and Google it, where I discover that's the hash of the number 115982. I look around on my account and don't see anywhere that number is used. I try requesting that page with the hash of 115983, 115984, and so on. They all take me to a page to reset someone else's password. I notice that I'm hitting the reset page for the users immediately after me. Using 115981 uses the one before me. My user ID is 112112, and 115982 - 112112 = 3870. I find the admin account, user ID 2, and try the page with MD5(2+3870). It works. I reset the admin password and I'm in. Now it does depend on what you do with the "display a form" part, but at least this entire encrypt=hash nonsense is useless. Edited December 19, 2014 by requinix Quote Link to comment Share on other sites More sharing options...
NotionCommotion Posted December 20, 2014 Author Share Posted December 20, 2014 Depends mostly on step 3, but let's just say "no": No to what, the technique given in the blog which I admitted in the beginning was very suspect? Or the steps I stated in my second post? If neither are correct, what are the correct steps? Quote Link to comment Share on other sites More sharing options...
requinix Posted December 20, 2014 Share Posted December 20, 2014 (edited) No to what, the technique given in the blog which I admitted in the beginning was very suspect?The thing I quoted, so those steps from the blog. Admittedly I was answering the question in my head and not quite the one you asked, so it should be more of a Depends mostly on step 3, but let's just say you should not do it that way: Here's how I would probably do it: 1. The link to go reset their password takes them to a page where they enter their whatever identifier (eg, username or email address). 2. Form stores a unique code with the account but doesn't reset the password yet. 3a. Send an email to the account with a link (including that code) to the next page. 3b. SMS would be a better alternative as it's much less likely an attacker also has the user's phone. Then the code, a short string of course, is required to continue to the next step. 4. Page asks questions that supposedly only the user knows the answers to. Possibly multiple pages. 5. After confirming all that information, allow them to change the password. You could actually skip the email step and go straight from 1 to 4, but I like requiring the email as it means an attacker also has to have control over the email account - which could already be the case, of course, so you don't rely on just that. Ditto for SMS except the phone is a real, physical device and not something on the internet. Usual other "gotchas" apply: don't reveal information like whether an account exists, don't let people brute-force this, etc. Edited December 20, 2014 by requinix Quote Link to comment Share on other sites More sharing options...
NotionCommotion Posted December 20, 2014 Author Share Posted December 20, 2014 SMS is an option which I didn't consider. Thank you. I noticed you recommend "secret questions" in step 4 which seem to no longer be in vogue. I agree that that one shouldn't rely on them (i.e. going from step 1 to step 4), but they likely provide additional screening, and allow a real person to get a feel when talking to them. Quote Link to comment Share on other sites More sharing options...
requinix Posted December 20, 2014 Share Posted December 20, 2014 I noticed you recommend "secret questions" in step 4 which seem to no longer be in vogue.Not the "secret question/secret answer" things. Those could have been good but everybody uses stupid ones like "What's the name of your pet?" and "Where did you grow up?" that even the most amateur stalker can find out pretty easily, so yes they're frowned upon now. I mean ask them about information about their account. Maybe they sent a PM to somebody recently. Maybe they were doing some shopping for a type of product. Maybe they were reading something on your site earlier. The point is it's information that you can't just find through Google. Quote Link to comment Share on other sites More sharing options...
Jacques1 Posted December 20, 2014 Share Posted December 20, 2014 The random tokens must not be stored in the database. They're password-equivalent, because knowing them gives you full access to the user accounts. So the tokens must be hashed just like passwords. You don't need bcrypt, though. Since random numbers are high-entropy input, a simple hash algorithm like SHA-256 is enough to make the result immune to brute-force attacks. As always, KISS (keep it short and simple): The user provides their username. You generate a cryptographically secure random token, hash it and store the hash together with the current timestamp. Then you send the token itself (not the hash) to the user by e-mail. The e-mail points the user to a password reset page where they enter the desired password. This password together with the token is sent to your server. You hash the token and check if the hash is present in your database and has neither expired (e. g. < 1 h) nor been used. If the token is valid, you reset the password and deactivate the token. All other features that have been suggested will cause trouble, so you have to carefully compare the problems with the benefits and decide if it makes sense to extend the basic functionality. For example, any kind of security questions means that there has to be a (human) supporter who helps users in case they have trouble with the answer. People will have trouble, because they'll misspell the answer, forget the answer, mix up multiple possible answers etc. Simply locking out those users is not an option on most websites, so a socially competent human needs to sit on the phone, talk to the user, carefully give hints and ultimately decide if the caller is legitimate. Big companies can provide this service, but smaller websites probably not. And of course the whole thing only makes sense in an enviroment where users are actually willing to go through this painful procedure to restore their account. SMS gateways charge money and need to be fully trusted, because they'll get all plaintext tokens. And of course SMS only makes sense on big websites and closed communities. Nobody is going to hand out their phone number to some random website on the Internet. So as great as those extra security features sound, they have specific requirements and problems. For an average web application, KISS. 1 Quote Link to comment Share on other sites More sharing options...
NotionCommotion Posted December 20, 2014 Author Share Posted December 20, 2014 The random tokens must not be stored in the database. ... Then you send the token itself (not the hash) to the user by e-mail. Any reason not to store the random token in the DB and email the hash? Not that it is any easier than doing the opposite, just curious. Quote Link to comment Share on other sites More sharing options...
Jacques1 Posted December 20, 2014 Share Posted December 20, 2014 You can't do that, because then an attacker simply has to obtain the plaintext string from the database and hash it to get the same token you sent to the user (which gives full access to the account). The hash in your scenario is actually entirely useless. It's a plaintext system. You also can't use the hash to look up the original token. So you'd have to hash every single token in your database every time a user comes along, because that's the only way to check if there's a match. Long story short, it doesn't work. The token is the secret which must be protected, and the hash is the less critical residue you keep on your server for validation. Just like with normal passwords. Quote Link to comment Share on other sites More sharing options...
NotionCommotion Posted December 20, 2014 Author Share Posted December 20, 2014 Thanks, I wasn't thinking straight. Quote Link to comment Share on other sites More sharing options...
NotionCommotion Posted January 9, 2015 Author Share Posted January 9, 2015 (edited) As always, KISS (keep it short and simple): The user provides their username. You generate a cryptographically secure random token, hash it and store the hash together with the current timestamp. Then you send the token itself (not the hash) to the user by e-mail. The e-mail points the user to a password reset page where they enter the desired password. This password together with the token is sent to your server. You hash the token and check if the hash is present in your database and has neither expired (e. g. < 1 h) nor been used. If the token is valid, you reset the password and deactivate the token. Just to confirm, the password reset page in step 3 should include a hidden input containing the token itself and not the hash of the token? Should only the most recent token be considered valid if the user reset their password multiple times and gets multiple emails? If so, I suppose I should check the token and inform the user to check their email if it is not the most recent before displaying the password reset page. Similarly, if the token hash doesn't exist in the DB, inform the user? Edited January 9, 2015 by NotionCommotion Quote Link to comment Share on other sites More sharing options...
Jacques1 Posted January 9, 2015 Share Posted January 9, 2015 Just to confirm, the password reset page in step 3 should include a hidden input containing the token itself and not the hash of the token? The password reset must be done with the actual token, yes. Applications typically embed the token into the URL of the reset link. For example: https://www.yoursite.com/password_reset?secret=2d6d4fa7ef9607ea11755960114eac8f. However, this is problematic, because URLs are often logged by the server (including the parameters), and users may not understand that the URL is secret and mustn't be shared. A more secure approach is to make an actual text field for the token and ask the user to copy the token from the e-mail. But of course this is not very convenient and can confuse inexperienced users, so you might want to choose the first approach nonetheless. Should only the most recent token be considered valid if the user reset their password multiple times and gets multiple emails? If so, I suppose I should check the token and inform the user to check their email if it is not the most recent before displaying the password reset page. Similarly, if the token hash doesn't exist in the DB, inform the user? Yes, only the latest token should be considered valid. And, yes, do inform the user about invalid tokens (e. g. truncated URL parameters), nonexistent tokens and expired tokens. Those are no secrets. Quote Link to comment Share on other sites More sharing options...
NotionCommotion Posted January 10, 2015 Author Share Posted January 10, 2015 Thanks Jacques, I actually changed the user response to differentiate between a non-existent hashed (link is incorrect) token and one that isn't 32 characters (are you sure you included all the characters in the link?) based on your advise. Also, I agree that caching or logging of URLs with secret tokens is problematic, however, ever user (potentially excluding you ) wants it, so hopefully they will immediately reset their password and make the token no longer valid. Quote Link to comment Share on other sites More sharing options...
Jacques1 Posted January 10, 2015 Share Posted January 10, 2015 The middle ground would be to put the token into the URL but start a timer as soon as the user has visited the page. For example: After the password reset mail has been sent, the user has to click on the link within, say, half an hour. But once they've clicked on the link, they have to complete the reset within, say, 5 minutes. This way the token won't be rotting in some log. If you do this, make sure to put a big notice on top of the page. Otherwise users may not understand the logic. Quote Link to comment Share on other sites More sharing options...
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.