1
\$\begingroup\$

Here is what the reset link looks like that goes in an email to the user:

www.webdev702.com/forgotPassword.php?empl_id=121&[email protected]&request_time=1432183745918&resetCode=MG4L0aBqim27wK9fAWcJ2XCBF

There are many parts to the link and they are:

  • empl_id: the id of the person matching the email address entered
  • email: email address of the user
  • request_time: a UTC time stamp of time the reset password was requested
  • reset_code: a random 25 alpha numeric code

The process works by someone entering their email in the forgot password box and hitting send, the system then looks for the user with a matching email to the one that was entered and if found it grabs the employee id (empl_id). It then creates a random 25 alpha numeric code and gets the current time in a UTC time stamp. It creates a document in a collection call forgot_pass with the empl_id, email, reset_code and request_time.

Once the document is created the system sends the user a email with the generated link.

When a user clicks the link in the email they are taken to the site and on page load it grabs the items from the URL. It then looks in the forgot_pass collection for a document that has a matching empl_id, email and reset_code. It needs all 3 to successfully find the document. If it finds it, it then makes sure that no more then 24 hours have passed since the request was made. If it's been over 24 hours they will get an error message and won't be able to reset password. Also if it can't find the document an error message is displayed and they can't reset.

If all the conditions are correct then the user will be able to reset their password. Upon success of resetting the password the users is taken to the login page and the forgot_pass document is deleted.

Bad Reset Code Link: www.webdev702.com/forgotPassword.php?empl_id=121&[email protected]&request_time=1432183745918&resetCode=MG4L0aBqim27wK9fbWcJ2XCBF

Past 24 Hours Link: www.webdev702. com/forgotPassword.php?empl_id=121&[email protected]&request_time=1431183745918&resetCode=MG4L0aBqim27wK9fAWcJ2XCBF

The reason I send all the data in the link is because I want to find the document that matches all 3 things. I don't want to just look for a matching code, I think that the chances of someone guessing it is unlikely, but being this will be used by enterprise companies I want it to be as secure as possible.

Would you say that my method of generating a reset link is secure? If not, please tell me what you would change and why.

function sendResetLink(){
	empl_email = $('#resetEmail').val();
	resetCode = createEncryptCode();
	empl_id = getUsersID(empl_email);
	requestTime = getRequestTime();
	link = "http://www.webdev702.com/forgotPassword.php?empl_id="+empl_id+"&email="+empl_email+"&request_time="+requestTime+"&resetCode="+resetCode;

	$.ajax({                            
    url: 'https://api.mongolab.com/api/1/databases/hiperhipo/collections/forgot_pass/?apiKey=Mgh1pWtPjeImQeh7CjsbixpkXG-Xniib',
    async: false,
    data: JSON.stringify({
        "empl_id": empl_id,
        "empl_email": empl_email,
        "reset_code": resetCode,
        "request_time": requestTime

    }),
  	type: "POST",
  	contentType: "application/json"
	}).success(function(returnedSkData) {
		sendLinktoUser(empl_email, empl_id, requestTime, resetCode, link);
	});
		

}

function getUsersID(empl_email){
	urlUser = 'https://api.mongolab.com/api/1/databases/hiperhipo/collections/users/?apiKey=Mgh1pWtPjeImQeh7CjsbixpkXG-Xniib&q={"email":"'+ empl_email +'"}';
 	var theID
  $.ajax( 
     { 
       url: urlUser,
       type: "GET",
       async: false,
       contentType: "application/json"
     }
  ).success(function(returnedUserData) {
  	theID = returnedUserData[0]["empl_id"];
  });
  return theID;
}

function createEncryptCode(){
	var text = "";
  var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";

  for( var i=0; i < 25; i++ )
    text += possible.charAt(Math.floor(Math.random() * possible.length));

  return text;
}

function getRequestTime(){
	var x = new Date();
  var n = x.getTime(); 

  return n;
}

function getUrlParameter(sParam){
  var sPageURL = window.location.search.substring(1);
  var sURLVariables = sPageURL.split('&');
  for (var i = 0; i < sURLVariables.length; i++) 
  {
      var sParameterName = sURLVariables[i].split('=');
      if (sParameterName[0] == sParam) 
      {
          return sParameterName[1];
      }
  }
}

function checkResetCodeandTime(empl_id, requestTime, resetCode){
	urlForgot = 'https://api.mongolab.com/api/1/databases/hiperhipo/collections/forgot_pass/?apiKey=Mgh1pWtPjeImQeh7CjsbixpkXG-Xniib&q={"empl_id":"'+ empl_id +'", "reset_code":"'+resetCode+'"}';
 
  $.ajax( 
     { 
       url: urlForgot,
       type: "GET",
       async: false,
       contentType: "application/json"
     }
  ).success(function(returnedUserData) {
  	curTime = getRequestTime();
  	timeDif = parseInt(curTime) - parseInt(requestTime);
  	//console.log(timeDif);

  	if(timeDif < 86000000){
  			if(returnedUserData != ""){
  		
		  	}else{
		  		$('#resetForm').hide();
		  		$('.login-form').append('<div class="alert alert-danger" role="alert">The link you clicked is no longer valid.</div>')
		  	}
  	}else{
  		$('#resetForm').hide();
		  $('.login-form').append('<div class="alert alert-danger" role="alert">The link you clicked is no longer valid. The 24 hour reset window has passed.</div>')
  	}
  	

  });
  
}

function sendLinktoUser(emplemail, emplid, request_time, reset_code, the_link){        

  var data = {
      empl_email: emplemail,
      theLink: the_link
  };

 
  $.ajax({
      type: "POST",
      url: "sendForgotPassLink.php",
      data: data,
      success: function(){
          console.log("THE POST WAS SUCCESSFUL")
      }
  });       

}


function newPassword(){
		event.preventDefault();
    newpass1 = $('#pass1').val();
    newpass2 = $('#pass2').val();
    empl_id =  $('#emplId').val();

    urlPass = 'https://api.mongolab.com/api/1/databases/hiperhipo/collections/users/?apiKey=Mgh1pWtPjeImQeh7CjsbixpkXG-Xniib&q={"empl_id":"' + empl_id + '"}';   
		urlForgot = 'https://api.mongolab.com/api/1/databases/hiperhipo/collections/forgot_pass/?apiKey=Mgh1pWtPjeImQeh7CjsbixpkXG-Xniib&q={"empl_id":"'+ empl_id +'"}';

    $.ajax( 
        { url: urlPass,
          type: "GET",
          async: false,
          contentType: "application/json" 
        }
    ).success(function(returnedPassData) {

        if(newpass1 == newpass2){
            var newHashPW = $.md5(newpass1);         
            $.ajax( 
                { url: urlPass,
                	async: false,
                  data: JSON.stringify( { "$set" : {  "password" : newHashPW } }  ),
                  type: "PUT",
                  contentType: "application/json" 
                }
            ).success(function(returnedHData) {
            		$('#successPWD').show();
            		$('#resetForm').hide();

            		$.ajax( 
		                { url: urlForgot,
		                	async: false,
		                  data: JSON.stringify( { "$set" : {  "reset_code" : "DELETED", "empl_id": "DELETED", "empl_email": "DELETED" } }  ),
		                  type: "PUT",
		                  contentType: "application/json" 
		                }
		            ).success(function(returnedHData) {
		            		
		            		window.location = "/index.php";
		                
		            })
            		// window.location = "/index.php";
                
            });
        }else{
            errormisspassHTML = '<div class="alert alert-danger" role="alert">Please verify your new passwords, their was a mismatch.</div>';
            $('#login-form').prepend(errormisspassHTML);
        }
    });
    
}
<link rel="stylesheet" href="http://www.webdev702.com/assets/css/plugins/pace.css">
<script src="http://www.webdev702.com/assets/js/pace.min.js"></script>
<link href="http://www.webdev702.com/assets/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="http://www.webdev702.com/assets/css/plugins/bootstrap-switch.min.css">
<link rel="stylesheet" href="http://www.webdev702.com/assets/css/plugins/ladda-themeless.min.css">

<link href="http://www.webdev702.com/assets/css/plugins/humane_themes/bigbox.css" rel="stylesheet">
<link href="http://www.webdev702.com/assets/css/plugins/humane_themes/libnotify.css" rel="stylesheet">
<link href="http://www.webdev702.com/assets/css/plugins/humane_themes/jackedup.css" rel="stylesheet">
<link href="http://www.webdev702.com/assets/css/style.css" rel="stylesheet">
<link href="http://www.webdev702.com/assets/css/responsive.css" rel="stylesheet">

<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="http://www.webdev702.com/assets/js/resetPassword.js"></script>
<script src="http://www.webdev702.com/assets/js/pages/login.js"></script>
<script src="http://www.webdev702.com/assets/js/lib/jquery.easing.js"></script>
<script src="http://www.webdev702.com/assets/js/bootstrap-switch.min.js"></script>
<script src="http://www.webdev702.com/assets/js/bootstrap-switch.min.js"></script>
<body class="login-screen blue-color">
	<div class="login-bg">	</div>
	<section>
	    <div class="container-fluid">
		<div class="row">
		    <div class="col-md-12">
		        <div class="login-box">
		            <div class="login-content">		                
		                <h3>Identify Yourself</h3>
		            </div>

		            <div class="login-form">		            		

		                <form action="index.php" method="POST" class="form-horizontal ls_form">
		                    <div class="input-group ls-group-input">
		                        <input class="form-control" type="text" placeholder="Username" name="login">
		                        <span class="input-group-addon"><i class="fa fa-user"></i></span>
		                    </div>


		                    <div class="input-group ls-group-input">

		                        <input type="password" placeholder="Password" name="password"
		                               class="form-control" value="">
		                        <span class="input-group-addon"><i class="fa fa-lock"></i></span>
		                    </div>

		                   
		                    <div class="input-group ls-group-input login-btn-box">
		                        <input type="submit" class="btn ls-dark-btn ladda-button col-md-12 col-sm-12 col-xs-12" value="Submit" data-style="slide-down" />
		                           

		                        <a class="forgot-password" href="javascript:void(0)">Forgot password</a>
		                    </div>
		                </form>
		            </div>
		            <div class="forgot-pass-box">
		                <form action="#" class="form-horizontal ls_form">
		                    <div class="input-group ls-group-input">
		                        <input class="form-control" id="resetEmail" type="text" placeholder="[email protected]">
		                        <span class="input-group-addon"><i class="fa fa-envelope"></i></span>
		                    </div>
		                    <div class="input-group ls-group-input login-btn-box">
		                        <button onclick="sendResetLink()" class="btn ls-dark-btn col-md-12 col-sm-12 col-xs-12">
		                            <i class="fa fa-rocket"></i> Send
		                        </button>

		                        <a class="login-view" href="javascript:void(0)">Login</a>

		                    </div>
		                </form>
		            </div>

		        </div>
		    </div>
		</div>
	    </div>
	    <p class="copy-right big-screen hidden-xs hidden-sm">
		<span>&#169;</span> Hiper Hipo <span class="footer-year">2015</span>
	    </p>
	</section>

	</body>

\$\endgroup\$
2
  • 2
    \$\begingroup\$ To be constructive to the question: I would suggest NOT adding the request time to the URL but rather storing it on the server and check if 24 hours have passed, so that nobody can attempt to fake this information by adding a little bit to the time. \$\endgroup\$
    – GiantTree
    May 21, 2015 at 19:05
  • \$\begingroup\$ Can the 25 alphanumeric code be brute-forced or will it be locked-out if more than N tries have passed? When in security land, don't re-invent the wheel! \$\endgroup\$
    – Mast
    May 21, 2015 at 19:22

2 Answers 2

1
\$\begingroup\$

Serious concerns:

  • The entire password reset logic is implemented in client side JavaScript:

    • Your logic should not be visible. It should be hidden at server side. It's a lot easier to craft malicious input triggering bugs in the code when the code is visible
    • Your mongolab api key is exposed
    • Some of your internal api is also revealed
  • Error prone code:

    • Duplicated magic strings: invite typos and mistakes when you make changes later. Instead of copy pasting stuff, you should derive from global constants

    • It's error prone to write large numbers literally like 86000000. It's better to write as a more readable mathematical expression, for example 60 * 60 * 24 * 1000. This way it's clear that it's the number of milliseconds in 24 hours, and I don't have to wonder if there ate the right number of zeros at the end of a large number.

\$\endgroup\$
1
\$\begingroup\$

Besides the things @janos already pointed out there are several other things which could compromise security. The two biggest issues I can see is that you expose to employee's id and email.

Consider the situation an employee requests a password reset from a public computer and another person checks the search history. Now both the employee's internal id and the employee's email address has been exposed in the URL. If your site login uses email address as login credentials you have unwillingly given away a correct username. Half of the information required for an attack has been given away... bad :(

I would recommend you generate a strong hash/token using existing solutions. There are several tutorials around regarding that. I would then save that token along with employee id in a separate database table. It is important you also specify the lifetime/expiration time of token so that the reset token doesn't linger around forever. You then send the token to the employee's email with a link that could look something like to following:

http://awesomeness.com/reset?token=strong_hash_token_here

When you employee clicks the link and arrive on your page you fetch the token from the URL and checks your database for a matching token. Then you check if it has exceeded its lifetime. If it has you send an error, otherwise you display a form for the employee to submit a new password. Remember to delete the token from the database once used. This effectively renders the token one-time use.

Now you have avoided exposing you the employee's internal id, email and hidden your internal logic.

Important

It is important to note that all this must be done server side. Client side validation can easily be bypassed by disabling Javascript etc.

Happy coding!

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.