1. --
    Devshed Expert (3500 - 3999 posts)

    Join Date
    Jul 2012
    Posts
    3,915
    Rep Power
    1045
    Yes, your code is unnecessarily long. There's no reason to store every single value in a variable before using it. Just pass the values directly to the methods/functions.
    The 6 worst sins of security ē How to (properly) access a MySQL database with PHP

    Why canít I use certain words like "drop" as part of my Security Question answers? There are certain words used by hackers to try to gain access to systems and manipulate data; therefore, the following words are restricted: "select," "delete," "update," "insert," "drop" and "null".
  2. Contributing User
    Devshed Newbie (0 - 499 posts)

    Join Date
    Feb 2013
    Posts
    311
    Rep Power
    8
    got it, here is the request (partial with some unnecessary long statements):

    PHP Code:
    <?php 

       
        
    require("common.php"); 
        require(
    "lib/rnum.php");
        require(
    "lib/mail.php");
        require(
    "lib/password.php"); 

       
        if(!empty(
    $_POST)) 
        { 
            
    $user_stmt $db->prepare(
            SELECT 
                id 
            FROM 
                users 
            WHERE 
                email = :email 
        '
    ); 
        
    $user_stmt->execute(array( 
            
    ':email' => $_POST['email'
        )); 
        
    $user_id $user_stmt->fetchColumn(); 
        if (
    $user_id
        { 
           
            
    $deactivation_stmt $db->prepare(
                UPDATE 
                    responses 
                SET 
                    active = 0 
                WHERE 
                    user = :user 
            '
    ); 
            
    $deactivation_stmt->execute(array( 
                
    ':user' => $user_id 
            
    )); 
            
                        
    $email $_POST['email'];
                        
    $password_token rnum();
     
                          
    $query 
                            INSERT INTO responses ( 
                             reset_key,
                            user,
                            secret,
                            request_timestamp,
                            request_ip
                            ) VALUES ( 
                            :reset_key,
                            :user,
                            :secret,
                            NOW(),
                            :request_ip
                            )"
    ;
     
                            
    $secret password_hash($password_tokenPASSWORD_BCRYPT, array("cost" => 10));  
                            
    $reset_key rnum();
                            
    $user $user_id['id'];
                            
    $request_ip getenv('REMOTE_ADDR');

                              
    $query_params = array( 
                                
    ':reset_key' => $reset_key,
                                
    ':user' => $user,
                                
    ':secret' => $secret,
                                
    ':request_ip' => $request_ip
                            
    ); 
                    
                            
    $stmt $db->prepare($query); 
                            
    $result $stmt->execute($query_params); 
            

                            
    $url 'http://www.domain.com/response_forgot_password.php?reset_key=';
                            
    $url .= $reset_key;
                            
    $url .= '&user=';
                            
    $url .= $user;
                            
    $url .= '&password_token=';
                            
    $url .= $password_token;

                            
    $mail_to      $email;
                            
    $mail_subject 'Forgot password';
                            
    $mail_body 'Click this link to reset your password: ';
                            
    $mail_body .= $url;

                            
    mail_f ($mail_to$mail_subject$mail_body);
                            
        } 
        else 
        { 
            
    $url 'http://www.domain.com/unsubscribe.php?email=';
            
    $url .= $email;
            
    $mail_to      $email;
            
    $mail_subject 'Forgot password';
            
    $mail_body 'Hi, this is a password request for an unregistered member.<br> If you don\'t want to receive any emails from us again, please click this link:<br>';
            
    $mail_body .= $url;

            
    mail_f ($mail_to$mail_subject$mail_body);
        } 
        echo 
    'An email with further instruction has been sent to ' htmlentities($_POST['email'], ENT_QUOTESENT_HTML401'UTF-8'); 
    }          
         
    ?> <html>
    <body>
    <form action="forgot_password.php" method="post"> 
        Email:
        <input type="text" name="email" value="" /> 
        <br /><br /> 
        <input type="submit" value="Request" /> 
    </form> 
    </body>
    </html>
    and the respond:
    PHP Code:
    <?php  

         
        
    require("common.php");  
        require(
    "lib/password.php"); 


        
    $reset_key $_GET["reset_key"]; 
        
    $user $_GET["user"]; 
        
    $password_token $_GET["password_token"]; 
         

        if(!empty(
    $_POST))  
        {  
            
    $reset_key $_POST['reset_key']; 
            
    $user $_POST['user']; 
            
    $password_token $_POST['password_token']; 

            
    $query "  
                SELECT  
                    user 
                    , secret
                    , request_timestamp
                FROM
                    responses 
                WHERE  
                    reset_key = :reset_key
                    AND user = :user
                    AND NOT used
                    AND active
                "
    ;
              
            
    $query_params = array(  
                
    ':reset_key' => $reset_key,
                
    ':user' => $user 
            
    );  
              
            
            
    $stmt $db->prepare($query);  
            
    $result $stmt->execute($query_params);  
             
            
    $row $stmt->fetch();  

            if(
    $row
            {             
                        
    $created DateTime::createFromFormat('Y-m-d G:i:s'$row['request_timestamp']);  
                        if ( 
    $created >= new DateTime('30 minutes ago') )  
                        { 
                            if ( 
    password_verify($password_token$row['secret']) ) 
                            { 
                                   
    $query "  
                                UPDATE users  
                                SET  
                                    password = :password 
                                WHERE 
                                id = :id  
                                "
    ;  
                 
                                  
    $hash password_hash($_POST['password'], PASSWORD_BCRYPT, array("cost" => 10));          
              
                                
    $query_params = array(  
                                    
    ':password' => $hash,  
                                    
    ':id' => $row['user'
                                );  
              
              
                                    
    $stmt $db->prepare($query);  
                                
    $result $stmt->execute($query_params);  
                            }  
                        }  
                         else 
                        { 
                            echo 
    "This token has already been used, expired or inactive, please request a new one"
                        } 
            } 
            else 
            { 
                echo 
    "This token has already been used, expired or inactive, please request a new one"
            } 
        } 
    ?> <html> 
    <body> 
                                            
    <form action="response_forgot_password.php" method="post">   
     New password: 
        <input type="text" name="password" value="" />  
        <br /><br />  
    <input type="hidden" name="reset_key" value="<?php echo $reset_key?>" /> 
    <input type="hidden" name="user" value="<?php echo $user?>" /> 
    <input type="hidden" name="password_token" value="<?php echo $password_token?>" /> 
        <input type="submit" value="Login" />  
    </form>  

    </body> 
    </html>
    the unsubscribe script is coming!
    Last edited by derplumo; April 30th, 2013 at 03:11 PM.
  3. Contributing User
    Devshed Newbie (0 - 499 posts)

    Join Date
    Feb 2013
    Posts
    311
    Rep Power
    8
    how could I make the system for unsubscribing? I was thinking about making a random number and then storing it, sending the random number and the mail via the url in the email, and then when the user clicks the link the mail is put into that table. In this way, nobody can easily block someone by altering the url...

    It could be better I think...
  4. --
    Devshed Expert (3500 - 3999 posts)

    Join Date
    Jul 2012
    Posts
    3,915
    Rep Power
    1045
    Originally Posted by derplumo
    It could be better I think...
    The random number is a good approach. However, you shouldn't store the actual email address, because that unnecessarily puts it in danger. Imagine somebody breaks into your database, and every poor soul who has clicked on your "unsubcribe" link get bombarded with spam. Instead, only store a hash of the email address -- no fancy algorithm needed, just use SHA-256 or something.

    Before you send the mail to an unregistered address, you hash the target address and check whether it exists in your table of unsubribed addresses.

    And the password verification in the "respond" script still needs an "else" part for the error message -- or even better, a redundancy-free structure:

    PHP Code:
    $reset_successful false;

    if(
    $row)
    {
        
    $created DateTime::createFromFormat('Y-m-d G:i:s'$row['request_timestamp']);
        if ( 
    $created >= new DateTime('30 minutes ago') && password_verify($password_token$row['secret']) )
        {
            
    // change password
            
    $reset_successful true;
        }
    }

    if (
    $reset_successful)
        echo 
    'Your password has been reset!';
    else
        echo 
    'This token is invalid or has . Your password has not been changed.'
    Apart from that, the code looks good. It still needs a lot of testing, of course. But if everything works like it should, you've done pretty much all you can for protecting your users and their privacy.
    The 6 worst sins of security ē How to (properly) access a MySQL database with PHP

    Why canít I use certain words like "drop" as part of my Security Question answers? There are certain words used by hackers to try to gain access to systems and manipulate data; therefore, the following words are restricted: "select," "delete," "update," "insert," "drop" and "null".
  5. Contributing User
    Devshed Newbie (0 - 499 posts)

    Join Date
    Feb 2013
    Posts
    311
    Rep Power
    8
    but when it's encrypted, how would I check if it is in the table?
  6. --
    Devshed Expert (3500 - 3999 posts)

    Join Date
    Jul 2012
    Posts
    3,915
    Rep Power
    1045
    You hash the email address and check whether the result matches any hash in the database.

    This works just like password hashes: When you check a password, you don't even know the original password. But you can still check it by comparing its hash with the stored hash in the database. If they match, the passwords are the same (with a very high probability).
    The 6 worst sins of security ē How to (properly) access a MySQL database with PHP

    Why canít I use certain words like "drop" as part of my Security Question answers? There are certain words used by hackers to try to gain access to systems and manipulate data; therefore, the following words are restricted: "select," "delete," "update," "insert," "drop" and "null".
  7. No Profile Picture
    Contributing User
    Devshed Frequenter (2500 - 2999 posts)

    Join Date
    Dec 2004
    Posts
    2,868
    Rep Power
    368
    so i have been meaning to read this thread for ages and i had some time today so did exactly that.

    Q: does looping through 655537 time not slow down the script somewhat especially in a website which can be used by many users at once?
  8. --
    Devshed Expert (3500 - 3999 posts)

    Join Date
    Jul 2012
    Posts
    3,915
    Rep Power
    1045
    Originally Posted by paulh1983
    Q: does looping through 655537 time not slow down the script somewhat especially in a website which can be used by many users at once?
    Being slow is the goal of every password hashing algorithm. Because the slower it is, the longer it takes for an attacker to try out possible passwords, and the harder it is to "crack" a password with brute force.

    If you're worried that the hashing is too slow or eats up too many of your own resources, professional algorithms like bcrypt (which is what you should use) let you adjust the cost factor. You should also limit the password length to make sure nobody sends you Megabytes of gibberish for hashing, which might result in a denial of service. You can and should also limit the login attempts to one in every 5 seconds or so.
    The 6 worst sins of security ē How to (properly) access a MySQL database with PHP

    Why canít I use certain words like "drop" as part of my Security Question answers? There are certain words used by hackers to try to gain access to systems and manipulate data; therefore, the following words are restricted: "select," "delete," "update," "insert," "drop" and "null".
  9. No Profile Picture
    Contributing User
    Devshed Frequenter (2500 - 2999 posts)

    Join Date
    Dec 2004
    Posts
    2,868
    Rep Power
    368
    another question:

    say you hash a password 100 times, someone else hashes it 1000 times etc.. Now how would hacker know how many times to "unhash" it? or even whether someone hashed it multiple times?
  10. --
    Devshed Expert (3500 - 3999 posts)

    Join Date
    Jul 2012
    Posts
    3,915
    Rep Power
    1045
    Originally Posted by paulh1983
    say you hash a password 100 times, someone else hashes it 1000 times etc.. Now how would hacker know how many times to "unhash" it? or even whether someone hashed it multiple times?
    That depends on the algorithm. bcrypt stores all hashing parameters (the used algorithm and the number of rounds) in the result string. Home-made algorithms usually rely on global parameters hard coded into the application.

    Note that keeping the parameters "secret" does not make the algorithm more secure. This is a common misconception called security through obscurity. Since the attacker has already gained access to the server, it won't take him/her very long to find out how exactly the hashing works. Security comes from well-tested and proven algorithms, not from obscuring the hashing procedure.
    The 6 worst sins of security ē How to (properly) access a MySQL database with PHP

    Why canít I use certain words like "drop" as part of my Security Question answers? There are certain words used by hackers to try to gain access to systems and manipulate data; therefore, the following words are restricted: "select," "delete," "update," "insert," "drop" and "null".
  11. Contributing User
    Devshed Newbie (0 - 499 posts)

    Join Date
    Feb 2013
    Posts
    311
    Rep Power
    8
    Jacques, how would the query look like when you check if a mail already has been stored in the table, hashed?
  12. --
    Devshed Expert (3500 - 3999 posts)

    Join Date
    Jul 2012
    Posts
    3,915
    Rep Power
    1045
    Code:
    SELECT
    	EXISTS (
    		SELECT
    			1
    		FROM
    			unsubscribed_email_addresses
    		WHERE
    			email_address = :hash
    	)
    with :hash being the (SHA-256) hash of the target email address.

    The result of the query is either 0 or 1 (or true or false in better database systems).

    If the hash exists, it means the email address has been unsubscribed and shouldn't receive any mails. If it doesn't exist, you can send the email.
    The 6 worst sins of security ē How to (properly) access a MySQL database with PHP

    Why canít I use certain words like "drop" as part of my Security Question answers? There are certain words used by hackers to try to gain access to systems and manipulate data; therefore, the following words are restricted: "select," "delete," "update," "insert," "drop" and "null".
  13. No Profile Picture
    Contributing User
    Devshed Frequenter (2500 - 2999 posts)

    Join Date
    Dec 2004
    Posts
    2,868
    Rep Power
    368
    thanks jacques for answering my questions and ofcourse e-reo for writing this.
  14. No Profile Picture
    Contributing User
    Devshed Frequenter (2500 - 2999 posts)

    Join Date
    Dec 2004
    Posts
    2,868
    Rep Power
    368
    sorry-- ignore
  15. Contributing User
    Devshed Newbie (0 - 499 posts)

    Join Date
    Feb 2013
    Posts
    311
    Rep Power
    8
    Originally Posted by Jacques1
    Code:
    SELECT
    	EXISTS (
    		SELECT
    			1
    		FROM
    			unsubscribed_email_addresses
    		WHERE
    			email_address = :hash
    	)
    with :hash being the (SHA-256) hash of the target email address.

    The result of the query is either 0 or 1 (or true or false in better database systems).

    If the hash exists, it means the email address has been unsubscribed and shouldn't receive any mails. If it doesn't exist, you can send the email.
    Is the salt every time the same or does the query already take care of that?

IMN logo majestic logo threadwatch logo seochat tools logo