Link to home
Start Free TrialLog in
Avatar of Crazy Horse
Crazy HorseFlag for South Africa

asked on

Password reset token in url issue

If a user clicks on "reset password" and enters their email address, they will receive an email with a code as well as a link to the reset page. There is also a cookie set which gives them a limited amount of time to reset their password. Everything is working but my concern is the code in the url.

If I replace the proper code in the URL with something else e.g.:

http://localhost/simpleblog/code_validate.php?email=myemail@gmail.com&code=ca6bcbfc607af157f6a0aa7bed914849bf394


with:

http://localhost/simpleblog/code_validate.php?email=myemail@gmail.com&code=12345

the page still shows up and allows me to enter a verification code. Should it not redirect me because 12345 is not actually the token that was created?

If using the bogus code in the URL string and I enter the correct token i.e.: ca6bcbfc607af157f6a0aa7bed914849bf394, it still then gives me success but I thought it should fail since the code doesn't also match the code in the URL?

if (isset($_COOKIE['temp_access_code'])) {
		
		if (!isset($_GET['email']) && !isset($_GET['code'])) {
			
			header("location:index.php");
			
		} else {
			
			if (empty($_GET['email']) || empty($_GET['code'])) {
				
				header("location:index.php");
				
			} else {
				
				
				if (isset($_POST['code'])) {
					
					$email = htmlentities($_GET['email']);
					
					$validation_code = htmlentities($_POST['code']);
					
					$sql = "SELECT userID FROM `users` WHERE identifier = '".$link->real_escape_string($validation_code)."' AND email = '".$link->real_escape_string($email)."' LIMIT 1";
					$result = $link->query($sql);
					if ($result->num_rows == 1 && $validation_code == $_GET['code']){

						
						header("location:reset.php");
					} else {
						
						echo "Sorry, incorrect validation code.";
						
					}
						 
				}
				
			}
			
		}
		
	}
				  

	 else {
	//your cookie expired
	header("location:recover.php");
}

Open in new window

Avatar of Olaf Doschke
Olaf Doschke
Flag of Germany image

>isset($_POST['code'])
...
>$validation_code = htmlentities($_POST['code']);

You can't use $_POST on a get request. A request can only be one of get or post. The rest of it looks ok.

Bye, Olaf.
Avatar of Crazy Horse

ASKER

Is this better? It seems to work but I always like to check that the code is okay while I am still learning php, and there isn't some huge gaping security hole.

if (isset($_COOKIE['temp_access_code'])) {
		
		if (!isset($_GET['email']) && !isset($_GET['code'])) {
			
			header("location:index.php");
			
		} else {
			
			if (empty($_GET['email']) || empty($_GET['code'])) {
				
				header("location:index.php");
				
			} else {
				
				
				if (isset($_POST['code'])) {
					
					$email = htmlentities($_GET['email']);
					
					$validation_code = htmlentities($_GET['code']);
					
					$sql = "SELECT userID FROM `users` WHERE identifier = '".$link->real_escape_string($_POST['code'])."' AND email = '".$link->real_escape_string($email)."' LIMIT 1";
					$result = $link->query($sql);
					if ($result->num_rows == 1 && $validation_code == $_POST['code']){

						
						header("location:reset.php");
					} else {
						
						echo "Sorry, incorrect validation code.";
						
					}
						 
				}
				
			}
			
		}
		
	}

Open in new window

Any  URL you send via email will be requested with a GET-method request.  Any GET request must be idempotent and nullipotent.  GET requests are appropriate when there is nothing in the data model that will change as a result of the request -- information only, search, load a form, etc.

POST, PUT and DELETE requests may change the data model.  In practice most PHP requests that submit form data for an action that changes the data model are POST method requests.

Refs:
http://php.net/manual/en/tutorial.forms.php
http://php.net/manual/en/language.variables.superglobals.php
http://php.net/manual/en/language.variables.external.php

This might be useful background information:
https://www.experts-exchange.com/articles/11271/Understanding-Client-Server-Protocols-and-Web-Applications.html

You're misusing htmlentities() in this script.  It's used before sending output to the client browser.  It has no place inside your code or in your database.  Just filter and escape the external data, and save exactly what the client sent you.  In the "view" portion of your application, you would use htmlentities() or htmlspecialchars().

Why not save the data after htmlspecialchars() encoding?  Because if you ever try to get a job as a PHP developer and you show them a code sample that is misdesigned, you'll be asked to explain it, and there is no good explanation for this!  Also, if you're working in teams, your team members who are working on the view will not be expecting to receive entity-encoded information from the model or the controller.  That's the view's job.  Htmlspecialchars() and htmlentities() are a mung -- not idempotent and cannot be run more than once on a string. The more you can separate the model-view-controller responsibilities, the better off you'll be.
Sorry, yes. I keep getting confused between that and SQL injection.

I need things in baby language, Ray :P

So, if I am going to output something I use htmlentities() and if I am going to query the database I use real_escape_string?

So, I shouldn't be using htmlentities() for :

$email = htmlentities($_GET['email']);
					
$validation_code = htmlentities($_GET['code']);

Open in new window


because I am not outputting them anywhere?
SOLUTION
Avatar of Olaf Doschke
Olaf Doschke
Flag of Germany image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
ASKER CERTIFIED SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
@Ray, that's a great explanation, thanks! I will do my best to stick to your guidelines when doing it in practice.
Hi Olaf, just to clarify. There will be a POST request on the same page as the GET request.

I am going to say this out loud to try explain to myself exactly what is happening so feel free to tell me I am wrong :)

Just to better explain what I am doing, a user clicks on the "forgot password" link on the website and they then enter their email address. They receive an email with a code and a link to the reset password page. The code URL is the same as the code that they received in their email i.e.:

http://localhost/simpleblog/code_validate.php?email=myemail@gmail.com&code=ca6bcbfc607af157f6a0aa7bed914849bf394

The code that they have to enter is ca6bcbfc607af157f6a0aa7bed914849bf394, which is the same as the URL string.

So, when they click on the link they go to a page with a single input. They have to input ca6bcbfc607af157f6a0aa7bed914849bf394 in the form and submit/post it to verify that it matches the one in the database and then they will if successful, be taken to another page to reset their password.
Well, you can skip the last step, they already submit the code via the link as GET element. And so just clicking the link would already confirm they have that mail adress, because a) the mail arrived and b) they are the only one having that link with that code attached. You could even leave out the mail address. The main thing you verify is they got the code.

For password reset you could continue with a security question they have set up while registering. But not ask for that reset code once more.

To also verify a cookie so you also know the mail arrived at the same computer having started the reset is worth calling it a two factor validation. But that's not necessarily happening, as a) the mail might take longer to arrive or  b) the user might not care to verify his mail directly within the session timeout and c) the user might not have registered via his standard browser, which is started by the link, so a new browser is causing a new session. But mainly it's not really a two factor validation of the correct user having started the password reset, as a hacked mail account means the hacker starts the reset, catches the mail and of course also has that same browser session, if he cares to.

What is important is, that you only present a form for entering a new password, after the user verified via some other factor like the security question I already mentioned or by entering a code (not that code) you sent via another channel, eg as SMS to his mobile, like a 5-8 digit one time PIN/TAN code, or you rely on one factor validation.

Reentering the code is no real secondary validation, it only validates the user is not a bot and understands the instruction to enter the same code, the secret security question is a better protection against someone having hacked the mail account.

Bye, Olaf.
That's a really good point,Olaf. I will definetely do that going forward, either the sms to mobile phone or security questions. Thanks for the suggestion!
+1 for two-factor authentication.  But in my experience, if the client forgot the password, they may also have forgotten the security question(s) and the whole exercise turns into a security nightmare that drives the client away.  I would use different levels of security if I were protecting bowling scores, an e-commerce site, medical records, nuclear launch codes, etc.  The minimum amount of "suck" is often the best design criterion.
Haha, I have had that more times than I count when I try to reset my password and I have forgotten the answers to my security questions. It is enough to make me want to throw my computer out the window! So yes, security nightmare is correct! The code sent to your phone seems like a less stressful way for the user.

Haha, love that cartoon by the way, lol! :)
Indeed, the two channels mail and phone already make it far less probable both get into the hands of a hacker - unless you receive mails on the same smartphone...

Sure, it depends on what level of security you want or need. Minimum amount of "suck" - well, I disagree about that, but even taking this as a valid point, how much something sucks also depends on how you implement it. And with the stories, which already happened even to companies like Google, people should care more about any accounts security, as low as the value of an account is, it can lead to more interesting knowledge, eg you may find something valuable in conversations.

Since this also is about you learning PHP for becoming a professinal developer, it doesn't hurt to have implemented such a feature in its entirety, and you should already have noticed in which way you can fool yourself about thinking of something more secure, which isn't more secure.

What sucks about security questions is, there are often predefined ones like the maiden name of your mother, which are not very safe as that can be found out. Best idea is to let a user define questions himself. And how hard it is for yourself is - sorry to say so - a bit depending on your own cleverness. You may even set it up as 1+1=?, if you don't provide the answer 2, but two or love, or even more clever, any code you may note somewhere on this thing you can write on - paper. It can be in your flat, that also means two factor security, somebody has to enter you mail account and your flat to crack this. The notice doesn't need to be so obvious as key to whatever website. And what is clever about a security question would be, it reminds you what the answer is, but only you, for example the security question could be a key you need to lookup the secret in your writing pad. The answer does not really need to be related, even when the question looks like this. The whole thing is just about the same cleverness and ideas you should put into creating a good password.

Bye, Olaf.