2

Im currently implementing a "Remember Me" system for a web project. After reading a few (albeit not so helpful articles" I came up with this system to prevent a cookie from being used to access an account other than that of the user that is was assigned to:

  1. User logs in and checks the "Remember Me" box.
  2. Users email and password combination are authenticated. Because the remember me function is checked a random salt 128 characters long is created and stored in a table along with the users userid. A cookie is set containing a hash of the users userid concatenated to the hash of the users userid and salt.
  3. When the user revisits the webpage the userid and hash are separated and the hash is check against a hash generated from the salt and userid stored in the database. If the values match then the user is the user the cookie was assigned to and the user is logged in.

If there are any issues/improvements that can be made please point them out Im always open to criticism :)

Sam Genest
  • 107
  • 1
  • 12
  • Sounds good to me. We assume you're doing this over https only. – Michael Berkowski Jun 18 '12 at 21:55
  • Ive accepted the security hole being created by the lack of HTTPS as a necessary evil at this point. The security isnt worth the overhead in the case of this project. – Sam Genest Jun 18 '12 at 22:13
  • first of I think you should never write your own (probably insecure)authentication system. Especially when you do not use HTTPS!! I think you should have a look at something like OpenID. – Alfred Jun 18 '12 at 23:59

5 Answers5

3

Your system has a good start, but is still very vulnerable to hijacking (if someone takes the hash that is stored in the cookie, he can easily take over the session).

Anyway, here are some good posts about this very same topic:

I think you should start by reading those links.

There is indeed not a complete foolproof system (check the 3rd and 4th links, they provide some interesting extra things; user-agent is some kind of identifier you can use as well). I can suggest you to use some kind of OpenID provider (or multiple) to get all the security stuff out of your hands. If do want to do it yourself: you'll have to weigh the pro's and con's to achieve maximum security or a little less.

Community
  • 1
  • 1
Styxxy
  • 7,462
  • 3
  • 40
  • 45
  • "if someone takes the hash that is stored in the cookie, he can easily take over the session" --- if someone takes the user's password they could authenticate as well. And it doesn't mean that login+password authentication is vulnerable itself – zerkms Jun 18 '12 at 21:58
  • Still, the cookie data can be accessed much easier than the username+password. In this case, you don't even need the username or password to just take over a session. – Styxxy Jun 18 '12 at 22:00
  • but anyway - following the link you gave I don't see any proposals to protect it – zerkms Jun 18 '12 at 22:01
  • From what Ive read theres no way to 'prevent' a session form being hijacked with a 'remember me' cookie of any sort the best you can do is prevent a cookie form being used to access any other accounts on the site. Also the first link seems to lack any sort of truly useful information mind pointing out what you feel is relevant? The second seems only to point out IP tracking as an additional confirmation tool and this is IMO to much hassle for those with non static IPs for my project security needs – Sam Genest Jun 18 '12 at 22:08
  • I added extra links. There is indeed not a complete foolproof system (check the 3rd and 4th links, they provide some interesting extra things; user-agent is some kind of identifier you can use as well). I can suggest you to use some kind of OpenID provider (or multiple) to get all the security stuff out of your hands. If do want to do it yourself: you'll have to weigh the pro's and con's to achieve maximum security or a little less. – Styxxy Jun 18 '12 at 22:13
  • At this time the major hole seems to be the lack of using HTTPS (even that has been hacked/sniffed in the past) the sole goal of what Im trying to implement is to ensure that a a hacker cannot access any accounts other then that of the user whos cookie was sniffed and thus keep any damage caused to a minimum. – Sam Genest Jun 18 '12 at 22:47
  • Then you are okay-ish with your solution. Maybe add some extra security checks using a user-agent check etc. Apart from that, you can add some kind of hacking detection as described in one of the links. And at last: you can also look into OpenID providers ;). – Styxxy Jun 18 '12 at 22:49
2

Like others have said you have a good start. I would recommend creating a table that stores some of the cookie information, in particular:

user_id
random_hash
ip_address
date_created

Then when the user logs in, you generate a hash like you said and insert that data into the table and create the cookie storing their user_id & the hash.

When the user returns you check the hash and ip_address and look for a match. If one is found, authenticate said user_id AND remove the used hash and generate a new one for next login. I'm sure you can see the benefits of doing this way.

Also - make sure your using SSL and (important) never allow a user to change or view any key information without providing a password!

Cheers!

Luke Pittman
  • 870
  • 4
  • 12
  • For the time being there is not really any information (besides a password but I think requring the old one is standard :p) that needs password protection. – Sam Genest Jun 18 '12 at 22:19
  • 1
    Fair enough - I feel it would not be a complete answer without providing that statement however as that is just a crucial as securing the cookie/session system. – Luke Pittman Jun 18 '12 at 22:20
1

Nope, it's overcomplicated.

  1. Create remember_me (or whatever table name you like) table in database with columns user_id | hash

  2. After user is authenticated with "remember me" option generate random hash and put it in table with correspondent user_id and in user's cookie

  3. When user comes to a page and is not authenticated and has "remember_me" cookie - find user_id by cookie value

The important thing: there is no valid reason ever to rely your remember_me client data on real user's name, email, password or anything

PS: you could append the client data with user_id value if you wish

zerkms
  • 249,484
  • 69
  • 436
  • 539
  • Its also not a bad idea to somehow include client IP to prevent cookie jacking since TCP will prevent IP spoofing. – jedwards Jun 18 '12 at 22:05
  • 2
    @jedwards: I would agree with only including 2 or 3 first octets. There are a lot of people with dynamic IP nowadays, and "remember me" assumes person will visit the site from day to day – zerkms Jun 18 '12 at 22:07
  • @zerkms Thats a great way to overcome dynamic IPs I will be implementing that shortly. Could you point out the specific differences between your solution and my own? Im not entirely sure of what they are :) – Sam Genest Jun 18 '12 at 22:17
  • @user1195999: seems like I initially got it wrong (language issues) because now I also don't see significant differences (only that you perform additional hashes, which aren't necessary and don't bring anything) – zerkms Jun 18 '12 at 22:28
  • Mine uses a random salt value with the user userid thus preventing someone from finding the proper hash algorithm and replicating the stored hash by using a specific users userid. Its also going to allow me to hash the IP stored in the cookie hopefully preventing a hacker form realizing that they need to spoof the IP as well. Dont worry about the language barrier it takes all kinds to make the internet :p – Sam Genest Jun 18 '12 at 22:40
  • @user1195999: instead of protecting hashing algorithm I propose to use random – zerkms Jun 18 '12 at 22:41
  • On second thought I am certainly not going to store the IP in the cookie xD – Sam Genest Jun 18 '12 at 23:00
0

Yes that sounds OK but why reinvent the wheel? You are describing Sessions. You may as well use the functionality built into PHP.

http://php.net/manual/en/features.sessions.php

Joe
  • 46,419
  • 33
  • 155
  • 245
  • True. But user1195999's proposed solution would require the same cookie to be set. – Joe Jun 18 '12 at 21:55
0

I think I've found a clever solution!

Advantages of this (complicated?) script:

  • When the user successfully logs in with Remember Me checked, a login cookie is issued in addition to the standard session management cookie.[2]

  • The login cookie contains the user's username, a series identifier, and a token. The series and token are unguessable random numbers from a suitably large space. All three are stored together in a database table.

  • When a non-logged-in user visits the site and presents a login cookie, the username, series, and token are looked up in the database.

  • If the triplet is present, the user is considered authenticated. The used token is removed from the database. A new token is generated, stored in database with the username and the same series identifier, and a new login cookie containing all three is issued to the user.

  • If the username and series are present but the token does not match, a theft is assumed. The user receives a strongly worded warning and all of the user's remembered sessions are deleted.

  • If the username and series are not present, the login cookie is ignored.

I've made a table in the database with the following information:

session | token | username | expire

The remember me cookie will have this setup:

$value = "$session|$token|$userhash"; //Total length = 106
  • Session will be a string of 40 (sha1) characters.
  • Token will be a string of 32 (md5) characters.
  • Userhash in the cookie will be a string of 32 (md5 of username) characters.
  • Username in the database will be the normal username.
  • Expire will be now + 60 days.

The script:

ini_set('session.hash_function', 'sha1');
ini_set('session.hash_bits_per_character', '4');

session_start();

if(isset($_POST['user']) && isset($_POST['password'])) {
    if(isset($_COOKIE['remember']) && strlen($_COOKIE['remember']) == 106) {
      //THERE is a cookie, which is the right length 40session+32token+32user+2'|'
      //Now lets go check it...
      //How do I protect this script form harmful user input?
      $plode = explode('|', $_COOKIE['remember']);
      $session = htmlspecialchars($plode[0]);
      $token = htmlspecialchars($plode[1]);
      $userhash = htmlspecialchars($plode[2]);
      $result_query = $auth->query("SELECT user
                  FROM sessions
                  WHERE session = '$session'
                  AND token = '$token'
                  AND user = '$userhash'");
      $result_array = array();

      $auth_query = $auth->query("SELECT user FROM sessions WHERE session = '$session' AND user = '$userhash'");
      $auth_array = array();

      while ($result_object = $result_query->fetch(PDO::FETCH_NUM)) {
          $result_array[] = $result_object;
      }

      while ($auth_object = $auth_query->fetch(PDO::FETCH_NUM)) {
          $auth_array[] = $auth_object;
      }

      if(count($result_array) > 0){
        if(isset($_COOKIE['PHPSESSID'])) {
          //COOKIE is completely valid!
          //Make a new cookie with the same session and another token.
          $newusername = $auth_array[0][0];
          $newsession = $session;
          $newtoken = md5(uniqid(rand(), true));
          $newuserhash = $newusername;
          $value = "$newsession|$newtoken|$newuserhash";
          $expire = time() + 4184000;
          setcookie('remember', $value, $expire, '/', 'spigotpool.ml', isset($_SERVER["HTTPS"]), true);
          $auth_query = $auth->prepare("UPDATE sessions
                  SET token = :newtoken, expire=:expire
                  WHERE session = :session
                  AND token = :token
                  AND user = :userhash");
          $auth_query->bindParam(':newtoken', $newtoken);
          $auth_query->bindParam(':expire', $expire);
          $auth_query->bindParam(':session', $session);
          $auth_query->bindParam(':token', $token);
          $auth_query->bindParam(':userhash', $userhash);
          $auth_query->execute();
          //Set-up the whole session (with user details from database) etc...
        }
      } else if(count($auth_array) == 1) {
          //TOKEN is different, session is valid
          //This user is probably under attack
          //Put up a warning, and let the user re-validate (login)
          //Remove the whole session (also the other sessions from this user?)
      } else {
          //Cookie expired in database? Unlikely...
          //Invalid in what way?
          //Make a new cookie with the same session and another token.
          $newusername = $_POST['user'];
          $newsession = session_id();
          $newtoken = md5(uniqid(rand(), true));
          $newuserhash = md5($newusername);
          $value = "$newsession|$newtoken|$newuserhash";
          $expire = time() + 4184000;
          setcookie('remember', $value, $expire, '/', 'www.example.com', isset($_SERVER["HTTPS"]), true);
          $auth->query("INSERT INTO sessions (token, expire, session, user) VALUES ('$newtoken', '$expire', '$newsession', '$newuserhash')");
          header('Location: index.php?action=logged-in');
      }
    } else {
      //No cookie, rest of the script
      //Make a new cookie with the same session and another token.
      $newusername = $_POST['user'];
      $newsession = session_id();
      $newtoken = md5(uniqid(rand(), true));
      $newuserhash = md5($newusername);
      $value = "$newsession|$newtoken|$newuserhash";
      $expire = time() + 4184000;
      setcookie('remember', $value, $expire, '/', 'www.example.com', isset($_SERVER["HTTPS"]), true);
      $auth->query("INSERT INTO sessions (token, expire, session, user) VALUES ('$newtoken', '$expire', '$newsession', '$newuserhash')");
      header('Location: index.php?action=logged-in');
    }
}

Advantages of the script:

  • Multiple login. You can create new sessions for each computer you're on.
  • Cookie and database will stay clean. Active users renew there cookie every login.
  • The session check at the beginning ensures that the database will not get useless requests.
  • If an attacker steals a cookie, it gets a new token, but not a new session. So when the real user visits the website with the old(invalid) token but WITH a valid user-session combination the user gets a warning of the potential theft. After re-validating by logging in a new session is created and the session the attacker holds is invalid. The re-validating ensures the victim really is the victim, and not the attacker.

Reference: http://jaspan.com/improved_persistent_login_cookie_best_practice