0

So basically, I'm trying to make a simple, yet secure, forgotten password script.

There are two scripts, one that allows the user to enter their email address. This will then send them an email with a link that they must visit to save their new password.

The second script is where the link leads to. This script saves the new password.

For security purposes, I made a new table within my database called 'token'. It has three fields; token, email, used. Token is a random generated string of 10 letters and numbers, email is just that users email address, and used is an integer of either 1 or 0 indicating whether or not the token has been used.

You will be able to understand far more of my structure once you read over the two scripts. They are not to long, and not complex at all.

What is going wrong

Okay, so there is only one small thing going wrong, and this is within the reset-password.php script. This is where the users come to after they receive the email. Basically, I type in a new password, and click 'Reset Password', yet nothing happens. No errors or confirmations are shown, along with nothing changing within my database. I can't seem to debug this, and have been searching and trying for hours now. All help and suggestions would be greatly appreciated.

Please try to keep in mind that I am still a newbie at PHP and MySQL. Been working with PHP for approximately 8 weeks now, and MySQL for only 2.

forgot-password.php

<?php
//Forgotten password script

    //Variable to save errors
    $errors = array();

    $email = $_POST['email'];

    include 'config.php';
    mysql_connect("$db_host", "$db_username", "$db_password")or die("cannot connect"); 
    mysql_select_db("$db_name")or die("cannot select DB");

    $query = "SELECT email FROM users WHERE email='" . $email . "'";
    $result = mysql_query($query);
    $num = mysql_num_rows($result);
    if($num==0)
    {
        echo ("<div style='color:red;'>Email address is not registered</div>");
        die();
    }

    $token = getRandomString(10);
    $query = "INSERT INTO tokens (token,email) VALUES ('".$token."','".$email."')";
    mysql_query($query);

    //function to renerate the token
    function getRandomString($length) 
    {
        $validCharacters = "ABCDEFGHIJKLMNPQRSTUXYVWZ123456789";
        $validCharNumber = strlen($validCharacters);
        $result = "";

        for ($i = 0; $i < $length; $i++) 
        {
            $index = mt_rand(0, $validCharNumber - 1);
            $result .= $validCharacters[$index];
        }
        return $result;
    }

    //Send the reset link to the user
    function mailresetlink($to,$token)
    {
        $subject = "Password Reset";
        $message = '
        <html>
        <head>
        <title>Password Reset</title>
        </head>
        <body>
        <p>Click on the given link to reset your password <a href="http://domain.com/reset-password.php?token='.$token.'">Reset Password</a></p>

        </body>
        </html>
        ';
        $headers = "MIME-Version: 1.0" . "\r\n";
        $headers .= "Content-type:text/html;charset=iso-8859-1" . "\r\n";
        $headers .= 'From: Password Reset <noreply@domain.com>' . "\r\n";

        if(mail($to,$subject,$message,$headers))
        {
            echo "We have sent the password reset link to your email at <strong>".$to."</strong>"; 
        }
    }

    //If email is posted, send the email
    if(isset($_POST['email']))
    {
        mailresetlink($email,$token);
    }


?>




<table align="center" style="padding-bottom:40px;">
    <form action="<?php $_SERVER['PHP_SELF']; ?>" method="post">
    <tr>
    <td>Email Address: </td>
    <td><input type="text" name="email" /></td>
    </tr>
    <tr>
    <td colspan="2" align="center"><input type="submit" value="Reset My Password" /></td></tr>
    <input type="hidden" name="register" value="TRUE" />
</form>
</table>

reset-password.php

<?php
//Reset password script

    $token = $_GET['token'];
    $email;

    include 'config.php';
    mysql_connect("$db_host", "$db_username", "$db_password") or die("cannot connect"); 
    mysql_select_db("$db_name")or die("cannot select DB");

    if(!isset($_POST['newpassword']))
    {
        $query = "SELECT email FROM tokens WHERE token='" . $token . "' AND used = 0";
        $result = mysql_query($query);
        while($row = mysql_fetch_array($result))
        {
            $email = $row['email'];
        }


        if ($email != '')
        {
            $_SESSION['email'] = $email;
        }
        else 
        {
            echo "Invalid link or Password already changed";
        }
    }


    $pass = $_POST['newpassword'];
    $email = $_SESSION['email'];

    //Save new password
    if(isset($_POST['newpassword']) && isset($_SESSION['email']))
    {
        $query = "UPDATE users SET password = SHA('$password') WHERE email='" . $email . "'";
        $result = mysql_query($query);
        if($result)
        {
            mysql_query("UPDATE tokens SET used=1 WHERE token='" . $token . "'");
        }
        echo "Your password has been changed successfully";
        if(!$result)
        {
            echo "An error occurred. Please try the again or contact us at admin@domain.com";
        }
    }

?>



<table align="center" style="padding-bottom:40px;">
    <form action="<?php $_SERVER['PHP_SELF']; ?>" method="post">
    <tr>
    <td>New Password:</td>
    <td><input type="password" name="newpassword" id="password"/></td>
    </tr>
    <tr>
    <td colspan="2" align="center"><input type="submit" value="Change Password"></td></tr>
    <input type="hidden" name="reset" value="TRUE" />
</form>
</table>

Please, if you need any more information or code, please do not hesitate to ask.

Thanks in advance!

Fizzix
  • 23,679
  • 38
  • 110
  • 176
  • The answer from Mitch should solve your problem. As already said the function is not secure, in an earlier answer it tried to point out how a [secure password-reset](http://stackoverflow.com/a/18331345/575765) procedure could look like. – martinstoeckli Aug 20 '13 at 09:22
  • @martinstoeckli - As you can see by the new comment I posted on Mitch's post, his answer unfortunately did not solve the problem I am having with my code. – Fizzix Aug 20 '13 at 09:32
  • In the first get request you get the email from the database and store it in the session, then in the second post request you read the email from the session. This is not necessary, read the email only when it is necessary (after submitting the form). The reason it doesn't work in your example is, that you didn't call `session_start()` at the begin of the script, but really, a session is not necessary here. – martinstoeckli Aug 20 '13 at 10:31

1 Answers1

5

I don't see anywhere where you are passing the token parameter to the server on the reset page after entering the new password parameter. You should have another hidden <input /> control, I would expect. $_SERVER['PHP_SELF'] does not return query string parameters. That is likely the cause of your current problem.

Specifically,

<table align="center" style="padding-bottom:40px;">
    <form action="<?php echo $_SERVER['PHP_SELF']; ?>" method="post">
    <tr>
    <td>New Password:</td>
    <td><input type="password" name="newpassword" id="password"/></td>
    </tr>
    <tr>
    <td colspan="2" align="center"><input type="submit" value="Change Password"></td></tr>
    <input type="hidden" name="reset" value="TRUE" />
</form>
</table>

should be

<table align="center" style="padding-bottom:40px;">
    <form action="<?php echo $_SERVER['PHP_SELF']; ?>" method="post">
    <tr>
    <td>New Password:</td>
    <td><input type="password" name="newpassword" id="password"/></td>
    </tr>
    <tr>
    <td colspan="2" align="center"><input type="submit" value="Change Password"></td></tr>
    <input type="hidden" name="reset" value="TRUE" />
    <input type="hidden" name="token" value="<?php echo $_REQUEST['token']; ?>" />
</form>
</table>

Make sure you also change any $_GET['token']s to $_REQUEST['token'] as it will be GET the first time, then POST the second.

That being said, your much larger problem is the ability for me to bypass all your security by specifying ' or 1=1 or ' as my token. Or, I could be mean and do a nice '; update users set password = SHA('IKnowThisPassword') where username = 'admin'; --

Moral of the story being parameterized SQL (How can I prevent SQL injection in PHP?)

Community
  • 1
  • 1
Mitch
  • 21,223
  • 6
  • 63
  • 86
  • echo is missing here – plain jane Aug 20 '13 at 04:44
  • Hey guys. Not to fussed about how strong the security is. This is more a simple uni task assigned to me from a professor of mine that just needs a simple working recovery system for one of his simple websites. Been working on this example for hours now, and I know it is fixable somehow... – Fizzix Aug 20 '13 at 04:47
  • @Mitch - Thanks for that! Although, after making your changes, I now get the error of "Invalid link or Password already changed". Not to sure why it is entering that 'else' statement... – Fizzix Aug 20 '13 at 05:26
  • Did you update the `$_GET` globals to be `$_REQUEST`? If not, that could cause the `$token` variable to be null which could cause that error. – Mitch Aug 20 '13 at 05:52
  • @Mitch - Within my first version, the $token variable was being saved perfectly fine. As you can see, the very start of my reset-password.php script is saving the token. I even tried to echo the token to see if it was saving correctly, and it was. But yes, within the updates that you told me to do, I made the changes correctly as you stated. The problem in the first place was not with the token, but within the very last 'if' statement within the reset-password.php script. – Fizzix Aug 20 '13 at 09:16
  • @fizzix - Maybe you missed the point, `$token = $_GET['token']` will read the token correctly the first time you call the form, because this is a get request. When you submit the form, the variable will be empty, because you would have to read from `$token = $_POST['token']`. Using `$token = $_REQUEST['token']` will work in both cases. – martinstoeckli Aug 20 '13 at 09:40
  • @martinstoeckli - Ahhh alright, thank you for that. I now understand the difference between GET and POST. Although, I changed `$token = $_GET['token'];` to `$token = $_REQUEST['token'];` and I also changed my form to how Mitch suggested. Although, this is still not working. I try to `echo` both the `email` and `token` straight away. They print fine firstly, although when I submit the form neither one of them prints. I added this `echo` inside both `if` statements (!isset and isset). Any suggestions? – Fizzix Aug 20 '13 at 09:56
  • I managed to find the error. After implementing Mitch's correction, I then found that the the `email` was not saving as a `session` once the form was submitted. I used a work around by making the `email` as another `hidden input`. This completely fixed my problem. Thanks guys! – Fizzix Aug 20 '13 at 10:30
  • @fizzix - This is actually a bad idea, read my other comment, it is not necessary at all to store the email address. Not only is it unnecessary, putting it in a hidden field is a security risk. I could then call your form and can see the email address. Now i could exchange the email before submitting the form, and therefore could reset the password of any user i wish. – martinstoeckli Aug 20 '13 at 10:48