Solved

Password reset token in url issue

Posted on 2016-09-03
13
24 Views
Last Modified: 2016-09-04
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

0
Comment
Question by:Black Sulfur
  • 6
  • 4
  • 3
13 Comments
 
LVL 29

Expert Comment

by:Olaf Doschke
ID: 41783178
>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.
0
 

Author Comment

by:Black Sulfur
ID: 41783190
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

0
 
LVL 108

Expert Comment

by:Ray Paseur
ID: 41783195
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.
0
 

Author Comment

by:Black Sulfur
ID: 41783201
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?
0
 
LVL 29

Assisted Solution

by:Olaf Doschke
Olaf Doschke earned 150 total points
ID: 41783206
No the essential thing to do is not using POST, only use GET here. Your request is only checking the database for the validation code. A GET request here is okay, so a link within an email submits the code as GET parameter. This request only is for validating the request comes from the mail you sent, so you then can continue with a next HTML form, which in turn may then do a POST submission.

But in this code you should use GET where you had POST, because no $_POST element will be set.

Using htmlentities or other means to prevent any injection attacks is good. You do SQL here, so an SQL injection attack is possible, unless you sanitize data.

What's wrong is your error is using POST variables, which will never be set with a click on a link in a mail, this will always only do a GET request.

Bye, Olaf.
0
 
LVL 108

Accepted Solution

by:
Ray Paseur earned 350 total points
ID: 41783247
@Black Sulfur:  You have a lot of moving parts in these questions, so I will try to deconstruct them a little bit.

When your script receives external input from any source (GET, POST, COOKIE, database, whatever) the information is tainted and therefore must be assumed to be an attack vector.  Filter this information according to rules that protect you from accepting anything except known good values.

When you want to put information into a database, via a query string, you must be sure that the information contains nothing that can disrupt the processing of the query string (Your query string is a computer program that drives the database engine). Stray apostrophes as well as other special characters have special meanings in query strings.  So if you want to use something like "O'Brien" in a database query (note the apostrophe in the name), you need to tell the database that the apostrophe is part of the data -- not part of the query string.  The real escape string  functions take care of this for you.

If you want to write information to the client browser, you must be careful that you do not write malicious JavaScript.  This could occur if (for example) you let me enter textual information into your database and I, being an asshole, entered malicious JavaScript. When someone requests my information, your web site might go get the malicious JavaScript and send it to that poor someone, who would now be attacked by my JavaScript, served up by your web site.  Because JavaScript must be enclosed in <script> tags, and because the angle-brackets are munged by htmlentities(), you (as a smart programmer) protect the client by turning the angle-brackets into the respective entities of &lt; and &gt;.  These entities will be rendered correctly for visual purposes, but will have no programmatic effect on the client browser.

Does that help?
0
How your wiki can always stay up-to-date

Quip doubles as a “living” wiki and a project management tool that evolves with your organization. As you finish projects in Quip, the work remains, easily accessible to all team members, new and old.
- Increase transparency
- Onboard new hires faster
- Access from mobile/offline

 

Author Comment

by:Black Sulfur
ID: 41783406
@Ray, that's a great explanation, thanks! I will do my best to stick to your guidelines when doing it in practice.
0
 

Author Comment

by:Black Sulfur
ID: 41783413
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.
0
 
LVL 29

Expert Comment

by:Olaf Doschke
ID: 41783427
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.
2
 

Author Comment

by:Black Sulfur
ID: 41783444
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!
0
 
LVL 108

Expert Comment

by:Ray Paseur
ID: 41783603
+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.
0
 

Author Comment

by:Black Sulfur
ID: 41783607
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! :)
0
 
LVL 29

Expert Comment

by:Olaf Doschke
ID: 41783636
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.
1

Featured Post

What Security Threats Are You Missing?

Enhance your security with threat intelligence from the web. Get trending threat insights on hackers, exploits, and suspicious IP addresses delivered to your inbox with our free Cyber Daily.

Join & Write a Comment

Suggested Solutions

Popularity Can Be Measured Sometimes we deal with questions of popularity, and we need a way to collect opinions from our clients.  This article shows a simple teaching example of how we might elect a favorite color by letting our clients vote for …
Developers of all skill levels should learn to use current best practices when developing websites. However many developers, new and old, fall into the trap of using deprecated features because this is what so many tutorials and books tell them to u…
Learn how to match and substitute tagged data using PHP regular expressions. Demonstrated on Windows 7, but also applies to other operating systems. Demonstrated technique applies to PHP (all versions) and Firefox, but very similar techniques will w…
The viewer will learn how to look for a specific file type in a local or remote server directory using PHP.

707 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

12 Experts available now in Live!

Get 1:1 Help Now