“Keep Me Logged In” - the best approach

后端 未结 12 1685
Happy的楠姐
Happy的楠姐 2020-11-22 08:30

My web application uses sessions to store information about the user once they\'ve logged in, and to maintain that information as they travel from page to page within the ap

12条回答
  •  孤独总比滥情好
    2020-11-22 08:53

    Introduction

    Your title “Keep Me Logged In” - the best approach make it difficult for me to know where to start because if you are looking at best approach then you would have to consideration the following :

    • Identification
    • Security

    Cookies

    Cookies are vulnerable, Between common browser cookie-theft vulnerabilities and cross-site scripting attacks we must accept that cookies are not safe. To help improve security you must note that php setcookies has additional functionality such as

    bool setcookie ( string $name [, string $value [, int $expire = 0 [, string $path [, string $domain [, bool $secure = false [, bool $httponly = false ]]]]]] )

    • secure (Using HTTPS connection)
    • httponly (Reduce identity theft through XSS attack)

    Definitions

    • Token ( Unpredictable random string of n length eg. /dev/urandom)
    • Reference ( Unpredictable random string of n length eg. /dev/urandom)
    • Signature (Generate a keyed hash value using the HMAC method)

    Simple Approach

    A simple solution would be :

    • User is logged on with Remember Me
    • Login Cookie issued with token & Signature
    • When is returning, Signature is checked
    • If Signature is ok .. then username & token is looked up in the database
    • if not valid .. return to login page
    • If valid automatically login

    The above case study summarizes all example given on this page but they disadvantages is that

    • There is no way to know if the cookies was stolen
    • Attacker may be access sensitive operations such as change of password or data such as personal and baking information etc.
    • The compromised cookie would still be valid for the cookie life span

    Better Solution

    A better solution would be

    • User is logged in and remember me is selected
    • Generate Token & signature and store in cookie
    • The tokens are random and are only valid for single autentication
    • The token are replace on each visit to the site
    • When a non-logged user visit the site the signature, token and username are verified
    • Remember me login should have limited access and not allow modification of password, personal information etc.

    Example Code

    // Set privateKey
    // This should be saved securely 
    $key = 'fc4d57ed55a78de1a7b31e711866ef5a2848442349f52cd470008f6d30d47282';
    $key = pack("H*", $key); // They key is used in binary form
    
    // Am Using Memecahe as Sample Database
    $db = new Memcache();
    $db->addserver("127.0.0.1");
    
    try {
        // Start Remember Me
        $rememberMe = new RememberMe($key);
        $rememberMe->setDB($db); // set example database
    
        // Check if remember me is present
        if ($data = $rememberMe->auth()) {
            printf("Returning User %s\n", $data['user']);
    
            // Limit Acces Level
            // Disable Change of password and private information etc
    
        } else {
            // Sample user
            $user = "baba";
    
            // Do normal login
            $rememberMe->remember($user);
            printf("New Account %s\n", $user);
        }
    } catch (Exception $e) {
        printf("#Error  %s\n", $e->getMessage());
    }
    

    Class Used

    class RememberMe {
        private $key = null;
        private $db;
    
        function __construct($privatekey) {
            $this->key = $privatekey;
        }
    
        public function setDB($db) {
            $this->db = $db;
        }
    
        public function auth() {
    
            // Check if remeber me cookie is present
            if (! isset($_COOKIE["auto"]) || empty($_COOKIE["auto"])) {
                return false;
            }
    
            // Decode cookie value
            if (! $cookie = @json_decode($_COOKIE["auto"], true)) {
                return false;
            }
    
            // Check all parameters
            if (! (isset($cookie['user']) || isset($cookie['token']) || isset($cookie['signature']))) {
                return false;
            }
    
            $var = $cookie['user'] . $cookie['token'];
    
            // Check Signature
            if (! $this->verify($var, $cookie['signature'])) {
                throw new Exception("Cokies has been tampared with");
            }
    
            // Check Database
            $info = $this->db->get($cookie['user']);
            if (! $info) {
                return false; // User must have deleted accout
            }
    
            // Check User Data
            if (! $info = json_decode($info, true)) {
                throw new Exception("User Data corrupted");
            }
    
            // Verify Token
            if ($info['token'] !== $cookie['token']) {
                throw new Exception("System Hijacked or User use another browser");
            }
    
            /**
             * Important
             * To make sure the cookie is always change
             * reset the Token information
             */
    
            $this->remember($info['user']);
            return $info;
        }
    
        public function remember($user) {
            $cookie = [
                    "user" => $user,
                    "token" => $this->getRand(64),
                    "signature" => null
            ];
            $cookie['signature'] = $this->hash($cookie['user'] . $cookie['token']);
            $encoded = json_encode($cookie);
    
            // Add User to database
            $this->db->set($user, $encoded);
    
            /**
             * Set Cookies
             * In production enviroment Use
             * setcookie("auto", $encoded, time() + $expiration, "/~root/",
             * "example.com", 1, 1);
             */
            setcookie("auto", $encoded); // Sample
        }
    
        public function verify($data, $hash) {
            $rand = substr($hash, 0, 4);
            return $this->hash($data, $rand) === $hash;
        }
    
        private function hash($value, $rand = null) {
            $rand = $rand === null ? $this->getRand(4) : $rand;
            return $rand . bin2hex(hash_hmac('sha256', $value . $rand, $this->key, true));
        }
    
        private function getRand($length) {
            switch (true) {
                case function_exists("mcrypt_create_iv") :
                    $r = mcrypt_create_iv($length, MCRYPT_DEV_URANDOM);
                    break;
                case function_exists("openssl_random_pseudo_bytes") :
                    $r = openssl_random_pseudo_bytes($length);
                    break;
                case is_readable('/dev/urandom') : // deceze
                    $r = file_get_contents('/dev/urandom', false, null, 0, $length);
                    break;
                default :
                    $i = 0;
                    $r = "";
                    while($i ++ < $length) {
                        $r .= chr(mt_rand(0, 255));
                    }
                    break;
            }
            return substr(bin2hex($r), 0, $length);
        }
    }
    

    Testing in Firefox & Chrome

    enter image description here

    Advantage

    • Better Security
    • Limited access for attacker
    • When cookie is stolen its only valid for single access
    • When next the original user access the site you can automatically detect and notify the user of theft

    Disadvantage

    • Does not support persistent connection via multiple browser (Mobile & Web)
    • The cookie can still be stolen because the user only gets the notification after the next login.

    Quick Fix

    • Introduction of approval system for each system that must have persistent connection
    • Use multiple cookies for the authentication

    Multiple Cookie Approach

    When an attacker is about to steal cookies the only focus it on a particular website or domain eg. example.com

    But really you can authenticate a user from 2 different domains (example.com & fakeaddsite.com) and make it look like "Advert Cookie"

    • User Logged on to example.com with remember me
    • Store username, token, reference in cookie
    • Store username, token, reference in Database eg. Memcache
    • Send refrence id via get and iframe to fakeaddsite.com
    • fakeaddsite.com uses the reference to fetch user & token from Database
    • fakeaddsite.com stores the signature
    • When a user is returning fetch signature information with iframe from fakeaddsite.com
    • Combine it data and do the validation
    • ..... you know the remaining

    Some people might wonder how can you use 2 different cookies ? Well its possible, imagine example.com = localhost and fakeaddsite.com = 192.168.1.120. If you inspect the cookies it would look like this

    enter image description here

    From the image above

    • The current site visited is localhost
    • It also contains cookies set from 192.168.1.120

    192.168.1.120

    • Only accepts defined HTTP_REFERER
    • Only accepts connection from specified REMOTE_ADDR
    • No JavaScript, No content but consist nothing rather than sign information and add or retrieve it from cookie

    Advantage

    • 99% percent of the time you have tricked the attacker
    • You can easily lock the account in the attacker first attempt
    • Attack can be prevented even before the next login like the other methods

    Disadvantage

    • Multiple Request to server just for a single login

    Improvement

    • Done use iframe use ajax

提交回复
热议问题