«Gardez-moi connecté» - la meilleure approche

257

Mon application Web utilise des sessions pour stocker des informations sur l'utilisateur une fois qu'il s'est connecté et pour conserver ces informations lors de ses déplacements de page en page dans l'application. Dans cette application spécifique, je le stockage user_id, first_nameet last_namede la personne.

Je voudrais offrir une option «Gardez-moi connecté» lors de la connexion qui mettra un cookie sur la machine de l'utilisateur pendant deux semaines, qui redémarrera sa session avec les mêmes détails à son retour dans l'application.

Quelle est la meilleure approche pour ce faire? Je ne veux pas les stocker user_iddans le cookie, car il semble que cela faciliterait la tâche d'un utilisateur pour essayer de forger l'identité d'un autre utilisateur.

Matthieu
la source

Réponses:

735

OK, permettez-moi de dire ceci sans ambages: si vous placez des données utilisateur, ou quoi que ce soit dérivé de données utilisateur dans un cookie à cet effet, vous faites quelque chose de mal.

Là. Je l'ai dit. Nous pouvons maintenant passer à la réponse réelle.

Quel est le problème avec le hachage des données utilisateur, demandez-vous? Eh bien, cela se résume à la surface d'exposition et à la sécurité à travers l'obscurité.

Imaginez une seconde que vous êtes un attaquant. Vous voyez un cookie cryptographique défini pour le souvenir de moi sur votre session. Il fait 32 caractères de large. Gee. C'est peut-être un MD5 ...

Imaginons également une seconde qu'ils connaissent l'algorithme que vous avez utilisé. Par exemple:

md5(salt+username+ip+salt)

Maintenant, tout ce qu'un attaquant doit faire est de forcer brutalement le "sel" (qui n'est pas vraiment un sel, mais plus à ce sujet plus tard), et il peut maintenant générer tous les faux jetons qu'il veut avec n'importe quel nom d'utilisateur pour son adresse IP! Mais forcer brutalement un sel est difficile, non? Absolument. Mais les GPU modernes sont extrêmement bons dans ce domaine. Et à moins que vous n'utilisiez suffisamment de hasard (faites-le assez grand), il va tomber rapidement, et avec lui les clés de votre château.

En bref, la seule chose qui vous protège est le sel, qui ne vous protège pas vraiment autant que vous le pensez.

Mais attendez!

Tout cela était supposé que l'attaquant connaît l'algorithme! Si c'est secret et déroutant, alors vous êtes en sécurité, non? FAUX . Cette ligne de pensée a un nom: la sécurité par l'obscurité , sur laquelle il ne faut JAMAIS se fier.

La meilleure façon

La meilleure façon est de ne jamais laisser les informations d'un utilisateur quitter le serveur, à l'exception de l'ID.

Lorsque l'utilisateur se connecte, générez un gros jeton aléatoire (128 à 256 bits). Ajoutez cela à une table de base de données qui mappe le jeton à l'ID utilisateur, puis envoyez-le au client dans le cookie.

Et si l'attaquant devine le jeton aléatoire d'un autre utilisateur?

Eh bien, faisons un peu de calcul ici. Nous générons un jeton aléatoire de 128 bits. Cela signifie qu'il y a:

possibilities = 2^128
possibilities = 3.4 * 10^38

Maintenant, pour montrer à quel point ce nombre est absurde, imaginons que chaque serveur sur Internet (disons 50 000 000 aujourd'hui) essaie de forcer ce nombre à un rythme de 1 000 000 000 par seconde chacun. En réalité, vos serveurs fondraient sous une telle charge, mais jouons cela.

guesses_per_second = servers * guesses
guesses_per_second = 50,000,000 * 1,000,000,000
guesses_per_second = 50,000,000,000,000,000

Donc, 50 quadrillions de suppositions par seconde. C'est rapide! Droite?

time_to_guess = possibilities / guesses_per_second
time_to_guess = 3.4e38 / 50,000,000,000,000,000
time_to_guess = 6,800,000,000,000,000,000,000

Donc 6,8 sextillions de secondes ...

Essayons de ramener cela à des chiffres plus amicaux.

215,626,585,489,599 years

Ou encore mieux:

47917 times the age of the universe

Oui, c'est 47917 fois l'âge de l'univers ...

En gros, ça ne va pas être fêlé.

Pour résumer:

La meilleure approche que je recommande est de stocker le cookie en trois parties.

function onLogin($user) {
    $token = GenerateRandomToken(); // generate a token, should be 128 - 256 bit
    storeTokenForUser($user, $token);
    $cookie = $user . ':' . $token;
    $mac = hash_hmac('sha256', $cookie, SECRET_KEY);
    $cookie .= ':' . $mac;
    setcookie('rememberme', $cookie);
}

Ensuite, pour valider:

function rememberMe() {
    $cookie = isset($_COOKIE['rememberme']) ? $_COOKIE['rememberme'] : '';
    if ($cookie) {
        list ($user, $token, $mac) = explode(':', $cookie);
        if (!hash_equals(hash_hmac('sha256', $user . ':' . $token, SECRET_KEY), $mac)) {
            return false;
        }
        $usertoken = fetchTokenByUserName($user);
        if (hash_equals($usertoken, $token)) {
            logUserIn($user);
        }
    }
}

Remarque: N'utilisez pas le jeton ou la combinaison utilisateur et jeton pour rechercher un enregistrement dans votre base de données. Veillez toujours à extraire un enregistrement en fonction de l'utilisateur et à utiliser une fonction de comparaison sans temporisation pour comparer le jeton récupéré par la suite. En savoir plus sur le chronométrage des attaques .

Maintenant, il est très important que ce SECRET_KEYsoit un secret cryptographique (généré par quelque chose comme /dev/urandomet / ou dérivé d'une entrée à haute entropie). En outre, GenerateRandomToken()doit être une source aléatoire forte ( mt_rand()n'est pas assez forte. Utilisez une bibliothèque, telle que RandomLib ou random_compat , ou mcrypt_create_iv()avec DEV_URANDOM) ...

Il hash_equals()s'agit d'empêcher les attaques de synchronisation . Si vous utilisez une version PHP inférieure à PHP 5.6, la fonction hash_equals()n'est pas prise en charge. Dans ce cas, vous pouvez remplacer hash_equals()par la fonction timingSafeCompare:

/**
 * A timing safe equals comparison
 *
 * To prevent leaking length information, it is important
 * that user input is always used as the second parameter.
 *
 * @param string $safe The internal (safe) value to be checked
 * @param string $user The user submitted (unsafe) value
 *
 * @return boolean True if the two strings are identical.
 */
function timingSafeCompare($safe, $user) {
    if (function_exists('hash_equals')) {
        return hash_equals($safe, $user); // PHP 5.6
    }
    // Prevent issues if string length is 0
    $safe .= chr(0);
    $user .= chr(0);

    // mbstring.func_overload can make strlen() return invalid numbers
    // when operating on raw binary strings; force an 8bit charset here:
    if (function_exists('mb_strlen')) {
        $safeLen = mb_strlen($safe, '8bit');
        $userLen = mb_strlen($user, '8bit');
    } else {
        $safeLen = strlen($safe);
        $userLen = strlen($user);
    }

    // Set the result to the difference between the lengths
    $result = $safeLen - $userLen;

    // Note that we ALWAYS iterate over the user-supplied length
    // This is to prevent leaking length information
    for ($i = 0; $i < $userLen; $i++) {
        // Using % here is a trick to prevent notices
        // It's safe, since if the lengths are different
        // $result is already non-0
        $result |= (ord($safe[$i % $safeLen]) ^ ord($user[$i]));
    }

    // They are only identical strings if $result is exactly 0...
    return $result === 0;
}
ircmaxell
la source
7
Mais cette approche ne signifie-t-elle pas que n'importe qui peut prendre ce nom d'utilisateur et ce cookie et se connecter en tant qu'utilisateur à partir de n'importe quel autre appareil?
simplifié
8
lol :-), notez que 47917 ans est le temps maximum pour deviner, le jeton aléatoire pourrait également être deviné en 1 heure.
storm_buster
33
C'est bizarre parce que votre code contredit votre réponse. Vous dites "si vous mettez des données utilisateur dans un cookie [...] vous faites quelque chose de mal", mais c'est exactement ce que fait votre code! N'est-il pas préférable de supprimer le nom d'utilisateur du cookie, de calculer le hachage sur le jeton uniquement (et peut-être d'ajouter l'adresse IP pour empêcher le vol de cookie), puis de faire fetchUsernameByToken au lieu de fetchTokenByUserName dans RememberMe ()?
Leven
9
Depuis PHP 5.6, hash_equals peut être utilisé pour empêcher les attaques de synchronisation lors des comparaisons de chaînes.
F21
5
@Levit, il empêche quelqu'un de prendre un jeton valide et de changer l'ID utilisateur qui lui est attaché.
ircmaxell
93

Avis de sécurité : baser le cookie sur un hachage MD5 de données déterministes est une mauvaise idée; il est préférable d'utiliser un jeton aléatoire dérivé d'un CSPRNG. Voir la réponse d' ircmaxell à cette question pour une approche plus sécurisée.

Habituellement, je fais quelque chose comme ça:

  1. L'utilisateur se connecte avec "me garder connecté"
  2. Créer une session
  3. Créez un cookie appelé QUELQUE CHOSE contenant: md5 (sel + nom d'utilisateur + ip + sel) et un cookie appelé quelque chose d'autre contenant id
  4. Stocker un cookie dans la base de données
  5. L'utilisateur fait des trucs et part ----
  6. L'utilisateur retourne, vérifier quelque chose Autre cookie, s'il existe, obtenir l'ancien hachage de la base de données pour cet utilisateur, vérifier le contenu du cookie QUELQUE CHOSE correspond au hachage de la base de données, qui doit également correspondre à un hachage nouvellement calculé (pour le ip) ainsi: cookieHash == databaseHash == md5 (sel + nom d'utilisateur + ip + sel), s'ils le font, passez à 2, s'ils ne le font pas 1

Bien sûr, vous pouvez utiliser différents noms de cookies, etc., vous pouvez également modifier un peu le contenu du cookie, assurez-vous simplement qu'il n'est pas facile à créer. Vous pouvez par exemple également créer un user_salt lorsque l'utilisateur est créé et également le mettre dans le cookie.

Vous pouvez également utiliser sha1 au lieu de md5 (ou à peu près n'importe quel algorithme)

Pim Jager
la source
30
Pourquoi inclure l'IP dans le hachage? Assurez-vous également d'inclure des informations d'horodatage dans le cookie et utilisez ces informations pour établir un âge maximal pour le cookie afin de ne pas créer un jeton d'identité qui soit bon pour l'éternité.
Scott Mitchell
4
@Abhishek Dilliwal: C'est un fil assez ancien mais je suis tombé dessus en cherchant la même réponse que Mathew. Je ne pense pas que l'utilisation de la session_ID fonctionnerait pour la réponse de Pim parce que vous ne pouvez pas vérifier le hachage db, le hachage de cookie et la session_ID actuelle car la session_ID change chaque session_start (); pensais juste que je soulignerais cela.
Partack
3
Je suis désolé d'être ennuyeux, mais quel est le but du deuxième cookie autre chose? Quel est l'identifiant dans ce cas? S'agit-il simplement d'un simple type de valeur "vrai / faux" pour indiquer si l'utilisateur souhaite utiliser la fonctionnalité de connexion permanente? Si oui, pourquoi ne pas simplement vérifier si le cookie QUELQUE CHOSE existe en premier lieu? Si l'utilisateur ne voulait pas que sa connexion persiste, le cookie SOMETHING ne serait pas là en premier lieu, n'est-ce pas? Enfin, générez-vous de nouveau le hachage dynamiquement et le comparez-vous au cookie et à la base de données comme mesure de sécurité supplémentaire?
itsmequinn
4
Le jeton doit être RANDOM, non connecté à l'utilisateur / son adresse IP / son agent utilisateur / quoi que ce soit. C'est une faille de sécurité majeure.
pamil
4
Pourquoi utilisez-vous deux sels? md5 (sel + nom d'utilisateur + ip + sel)
Aaron Kreider
77

introduction

Votre titre «Keep Me Logged In» - la meilleure approche, il m'est difficile de savoir par où commencer, car si vous cherchez la meilleure approche, vous devrez tenir compte des éléments suivants:

  • Identification
  • Sécurité

Biscuits

Les cookies sont vulnérables. Entre les vulnérabilités courantes de vol de cookies du navigateur et les attaques de script intersites, nous devons accepter que les cookies ne sont pas sûrs. Pour aider à améliorer la sécurité, vous devez noter qu'il php setcookiesa des fonctionnalités supplémentaires telles que

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

  • sécurisé (à l'aide d'une connexion HTTPS)
  • httponly (Réduisez le vol d'identité grâce aux attaques XSS)

Définitions

  • Jeton (chaîne aléatoire imprévisible de longueur n, par exemple. / Dev / urandom)
  • Référence (chaîne aléatoire imprévisible de longueur n, par exemple. / Dev / urandom)
  • Signature (générer une valeur de hachage à clé à l'aide de la méthode HMAC)

Approche simple

Une solution simple serait:

  • L'utilisateur est connecté avec Remember Me
  • Cookie de connexion émis avec jeton et signature
  • À son retour, la signature est vérifiée
  • Si la signature est correcte .. le nom d'utilisateur et le jeton sont recherchés dans la base de données
  • si non valide .. retour à la page de connexion
  • Si valide, connectez-vous automatiquement

L'étude de cas ci-dessus résume tous les exemples donnés sur cette page, mais leurs inconvénients sont que

  • Il n'y a aucun moyen de savoir si les cookies ont été volés
  • L'attaquant peut accéder à des opérations sensibles telles que le changement de mot de passe ou des données telles que des informations personnelles et de cuisson, etc.
  • Le cookie compromis serait toujours valide pour la durée de vie du cookie

Meilleure solution

Une meilleure solution serait

  • L'utilisateur est connecté et se souvenir de moi est sélectionné
  • Générer un jeton et une signature et stocker dans un cookie
  • Les jetons sont aléatoires et ne sont valables que pour une authentification unique
  • Les jetons sont remplacés à chaque visite sur le site
  • Lorsqu'un utilisateur non connecté visite le site, la signature, le jeton et le nom d'utilisateur sont vérifiés
  • N'oubliez pas que la connexion doit avoir un accès limité et ne pas autoriser la modification du mot de passe, des informations personnelles, etc.

Exemple de 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());
}

Classe utilisée

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);
    }
}

Test dans Firefox et Chrome

entrez la description de l'image ici

Avantage

  • Meilleure sécurité
  • Accès limité pour l'attaquant
  • Lorsque le cookie est volé, il n'est valable que pour un accès unique
  • La prochaine fois que l'utilisateur d'origine accède au site, vous pouvez automatiquement détecter et informer l'utilisateur du vol

Désavantage

  • Ne prend pas en charge la connexion persistante via plusieurs navigateurs (Mobile et Web)
  • Le cookie peut toujours être volé car l'utilisateur ne reçoit la notification qu'après la prochaine connexion.

Solution rapide

  • Introduction d'un système d'approbation pour chaque système qui doit avoir une connexion persistante
  • Utilisez plusieurs cookies pour l'authentification

Approche des cookies multiples

Lorsqu'un attaquant est sur le point de voler des cookies, il suffit de le concentrer sur un site Web ou un domaine particulier, par exemple. exemple.com

Mais vraiment, vous pouvez authentifier un utilisateur de 2 domaines différents ( example.com et fakeaddsite.com ) et le faire ressembler à "Cookie d'annonce"

  • Utilisateur connecté à example.com avec Remember me
  • Nom d'utilisateur, jeton et référence dans le cookie
  • Stockez le nom d'utilisateur, le jeton, la référence dans la base de données, par exemple. Memcache
  • Envoyer l'identifiant de réfrence via get et iframe à fakeaddsite.com
  • fakeaddsite.com utilise la référence pour extraire l'utilisateur et le jeton de la base de données
  • fakeaddsite.com stocke la signature
  • Lorsqu'un utilisateur renvoie des informations de signature d'extraction avec iframe de fakeaddsite.com
  • Combinez les données et faites la validation
  • ..... vous connaissez le reste

Certaines personnes pourraient se demander comment utiliser 2 cookies différents? Eh bien, c'est possible, imaginez example.com = localhostet fakeaddsite.com = 192.168.1.120. Si vous inspectez les cookies, cela ressemblerait à ceci

entrez la description de l'image ici

De l'image ci-dessus

  • Le site actuellement visité est localhost
  • Il contient également des cookies définis à partir de 192.168.1.120

192.168.1.120

  • Accepte uniquement défini HTTP_REFERER
  • Accepte uniquement la connexion spécifiée REMOTE_ADDR
  • Pas de JavaScript, pas de contenu mais rien de plus que de signer des informations et de les ajouter ou de les récupérer à partir des cookies

Avantage

  • 99% pour cent du temps où vous avez trompé l'attaquant
  • Vous pouvez facilement verrouiller le compte lors de la première tentative de l'attaquant
  • L'attaque peut être évitée avant même la prochaine connexion comme les autres méthodes

Désavantage

  • Demande multiple au serveur juste pour une seule connexion

Amélioration

  • Utiliser l'iframe ajax
Baba
la source
5
Même si @ircmaxell a très bien décrit la théorie, je préfère cette approche, car elle fonctionne très bien sans avoir besoin de stocker l'ID utilisateur (ce qui serait une divulgation indésirable) et comprend également plus d'empreintes digitales que l'ID utilisateur et le hachage pour identifier le utilisateur, tel que le navigateur. Cela rend encore plus difficile pour un attaquant d'utiliser un cookie volé. C'est l'approche la meilleure et la plus sûre que j'ai vue jusqu'à présent. +1
Marcello Mönkemeyer
6

J'ai posé un angle de cette question ici , et les réponses vous mèneront à tous les liens de cookies de temporisation basés sur des jetons dont vous avez besoin.

Fondamentalement, vous ne stockez pas l'ID utilisateur dans le cookie. Vous stockez un jeton unique (énorme chaîne) que l'utilisateur utilise pour récupérer son ancienne session de connexion. Ensuite, pour le rendre vraiment sécurisé, vous demandez un mot de passe pour les opérations lourdes (comme changer le mot de passe lui-même).

Dan Rosenstark
la source
6

Vieux fil, mais toujours une préoccupation valable. J'ai remarqué de bonnes réponses au sujet de la sécurité et en évitant d'utiliser la «sécurité par l'obscurité», mais les méthodes techniques fournies n'étaient pas suffisantes à mes yeux. Choses que je dois dire avant de contribuer ma méthode:

  • Ne stockez JAMAIS un mot de passe en texte clair ... JAMAIS!
  • Ne stockez JAMAIS le mot de passe haché d'un utilisateur dans plusieurs emplacements de votre base de données. Votre serveur principal est toujours capable d'extraire le mot de passe haché de la table des utilisateurs. Il n'est pas plus efficace de stocker des données redondantes au lieu de transactions DB supplémentaires, l'inverse est vrai.
  • Votre identifiant de session doit être unique, afin que deux utilisateurs ne puissent jamais partager un identifiant, d'où l'objectif d'un identifiant (le numéro d'identification de votre permis de conduire pourrait-il correspondre à une autre personne? Non.) Cela génère une combinaison unique en deux parties, basée sur 2 cordes uniques. Votre table Sessions doit utiliser l'ID comme PK. Pour autoriser la confiance de plusieurs appareils pour la connexion automatique, utilisez une autre table pour les appareils approuvés qui contient la liste de tous les appareils validés (voir mon exemple ci-dessous) et est mappée à l'aide du nom d'utilisateur.
  • Il ne sert à rien de hacher des données connues dans un cookie, le cookie peut être copié. Ce que nous recherchons, c'est un appareil utilisateur conforme pour fournir des informations authentiques qui ne peuvent pas être obtenues sans qu'un attaquant compromette la machine de l'utilisateur (encore une fois, voir mon exemple). Cela signifierait, cependant, qu'un utilisateur légitime qui interdit aux informations statiques de sa machine (c'est-à-dire l'adresse MAC, le nom d'hôte de l'appareil, l'agent utilisateur s'il est restreint par le navigateur, etc.) de rester cohérent (ou usurpe en premier lieu) ne sera pas en mesure de utilisez cette fonction. Mais si cela vous inquiète, tenez compte du fait que vous offrez la connexion automatique aux utilisateurs qui s'identifient de manière unique, donc s'ils refusent d'être connus en usurpant leur MAC, en usurpant leur agent utilisateur, en usurpant / modifiant leur nom d'hôte, en se cachant derrière des proxys, etc., ils ne sont pas identifiables et ne devraient jamais être authentifiés pour un service automatique. Si vous le souhaitez, vous devez examiner l'accès aux cartes à puce fourni avec un logiciel côté client qui établit l'identité du périphérique utilisé.

Cela étant dit, il existe deux excellents moyens de se connecter automatiquement à votre système.

Tout d'abord, le moyen facile et bon marché qui met tout cela sur quelqu'un d'autre. Si vous faites en sorte que votre site prenne en charge la connexion avec, par exemple, votre compte google +, vous disposez probablement d'un bouton google + rationalisé qui connectera l'utilisateur s'il est déjà connecté à google (je l'ai fait ici pour répondre à cette question, car je suis toujours connecté à Google). Si vous souhaitez que l'utilisateur se connecte automatiquement s'il est déjà connecté avec un authentificateur approuvé et pris en charge, et que vous avez coché la case pour ce faire, demandez à vos scripts côté client d'exécuter le code derrière le bouton "Connexion avec" correspondant avant le chargement , assurez-vous simplement que le serveur stocke un ID unique dans une table de connexion automatique contenant le nom d'utilisateur, l'ID de session et l'authentificateur utilisés pour l'utilisateur. Étant donné que ces méthodes de connexion utilisent AJAX, vous attendez quand même une réponse, et cette réponse est soit une réponse validée, soit un rejet. Si vous obtenez une réponse validée, utilisez-la normalement, puis continuez de charger l'utilisateur connecté normalement. Sinon, la connexion a échoué, mais ne le dites pas à l'utilisateur, continuez comme non connecté, ils le remarqueront. Il s'agit d'empêcher un attaquant qui a volé des cookies (ou les a falsifiés dans le but d'augmenter ses privilèges) d'apprendre que l'utilisateur se connecte automatiquement au site.

C'est bon marché, et pourrait également être considéré comme sale par certains, car il essaie de valider votre auto déjà signé avec des endroits comme Google et Facebook, sans même vous le dire. Il ne doit cependant pas être utilisé sur les utilisateurs qui n'ont pas demandé à se connecter automatiquement à votre site, et cette méthode particulière est uniquement destinée à l'authentification externe, comme avec Google+ ou FB.

Étant donné qu'un authentificateur externe a été utilisé pour indiquer au serveur en arrière-plan si un utilisateur a été validé ou non, un attaquant ne peut obtenir autre chose qu'un ID unique, ce qui est inutile en soi. Je vais élaborer:

  • L'utilisateur 'joe' visite le site pour la première fois, l'ID de session est placé dans le cookie 'session'.
  • L'utilisateur «joe» se connecte, augmente les privilèges, obtient un nouvel ID de session et renouvelle le cookie «session».
  • L'utilisateur «joe» choisit de se connecter automatiquement à l'aide de google +, obtient un identifiant unique placé dans le cookie «keepmesignedin».
  • L'utilisateur «joe» les a gardés connectés à Google, permettant à votre site de se connecter automatiquement à l'utilisateur à l'aide de Google dans votre backend.
  • L'attaquant essaie systématiquement des identifiants uniques pour «keepmesignedin» (c'est une information publique remise à chaque utilisateur), et n'est connecté à aucun autre endroit; essaie l'ID unique donné à «joe».
  • Le serveur reçoit un ID unique pour «joe», extrait la correspondance dans la base de données pour un compte Google +.
  • Le serveur envoie l'attaquant à la page de connexion qui exécute une demande AJAX à Google pour se connecter.
  • Le serveur Google reçoit une demande, utilise son API pour voir que l'attaquant n'est pas connecté actuellement.
  • Google envoie une réponse selon laquelle il n'y a actuellement aucun utilisateur connecté via cette connexion.
  • La page de l'attaquant reçoit une réponse, le script redirige automatiquement vers la page de connexion avec une valeur POST encodée dans l'url.
  • La page de connexion obtient la valeur POST, envoie le cookie pour 'keepmesignedin' à une valeur vide et une date de validité jusqu'au 1-1-1970 pour dissuader une tentative automatique, ce qui oblige le navigateur de l'attaquant à supprimer simplement le cookie.
  • L'attaquant reçoit une page de connexion normale pour la première fois.

Quoi qu'il en soit, même si un attaquant utilise un ID qui n'existe pas, la tentative doit échouer à toutes les tentatives sauf lorsqu'une réponse validée est reçue.

Cette méthode peut et doit être utilisée conjointement avec votre authentificateur interne pour ceux qui se connectent à votre site à l'aide d'un authentificateur externe.

=========

Maintenant, pour votre propre système d'authentification qui peut connecter automatiquement les utilisateurs, voici comment je le fais:

DB a quelques tableaux:

TABLE users:
UID - auto increment, PK
username - varchar(255), unique, indexed, NOT NULL
password_hash - varchar(255), NOT NULL
...

Notez que le nom d'utilisateur peut contenir 255 caractères. Mon programme serveur limite les noms d'utilisateur de mon système à 32 caractères, mais les authentificateurs externes peuvent avoir des noms d'utilisateur avec leur @ domain.tld plus grands que cela, donc je ne supporte que la longueur maximale d'une adresse e-mail pour une compatibilité maximale.

TABLE sessions:
session_id - varchar(?), PK
session_token - varchar(?), NOT NULL
session_data - MediumText, NOT NULL

Notez qu'il n'y a pas de champ utilisateur dans ce tableau, car le nom d'utilisateur, une fois connecté, se trouve dans les données de session et le programme n'autorise pas les données nulles. Le session_id et le session_token peuvent être générés en utilisant des hachages aléatoires md5, des hachages sha1 / 128/256, des tampons datetime avec des chaînes aléatoires ajoutées puis hachés, ou tout ce que vous voulez, mais l'entropie de votre sortie doit rester aussi élevée que tolérable pour atténuer les attaques par force brute de même décoller, et tous les hachages générés par votre classe de session doivent être vérifiés pour les correspondances dans le tableau des sessions avant d'essayer de les ajouter.

TABLE autologin:
UID - auto increment, PK
username - varchar(255), NOT NULL, allow duplicates
hostname - varchar(255), NOT NULL, allow duplicates
mac_address - char(23), NOT NULL, unique
token - varchar(?), NOT NULL, allow duplicates
expires - datetime code

De par leur nature, les adresses MAC sont censées être UNIQUES, il est donc logique que chaque entrée ait une valeur unique. Les noms d'hôtes, en revanche, pourraient être dupliqués légitimement sur des réseaux séparés. Combien de personnes utilisent "Home-PC" comme nom de leur ordinateur? Le nom d'utilisateur est extrait des données de session par le serveur principal, il est donc impossible de le manipuler. Quant au jeton, la même méthode pour générer des jetons de session pour les pages doit être utilisée pour générer des jetons dans les cookies pour la connexion automatique de l'utilisateur. Enfin, le code datetime est ajouté lorsque l'utilisateur doit revalider ses informations d'identification. Soit mettre à jour ce datetime lors de la connexion de l'utilisateur en le conservant dans quelques jours, soit le forcer à expirer indépendamment de la dernière connexion en le conservant seulement pendant un mois environ, selon votre conception.

Cela empêche quelqu'un d'usurper systématiquement le MAC et le nom d'hôte d'un utilisateur qu'il connaît se connecte automatiquement. JAMAISdemander à l'utilisateur de conserver un cookie avec son mot de passe, du texte en clair ou autre. Faites régénérer le jeton sur chaque navigation de page, comme vous le feriez pour le jeton de session. Cela réduit considérablement la probabilité qu'un attaquant puisse obtenir un cookie de jeton valide et l'utiliser pour se connecter. Certaines personnes tenteront de dire qu'un attaquant pourrait voler les cookies de la victime et effectuer une attaque de relecture de session pour se connecter. Si un attaquant pouvait voler les cookies (ce qui est possible), il aurait certainement compromis l'ensemble de l'appareil, ce qui signifie qu'il pourrait simplement utiliser l'appareil pour se connecter de toute façon, ce qui va à l'encontre du but de voler complètement les cookies. Tant que votre site fonctionne sur HTTPS (ce qu'il devrait faire lorsqu'il s'agit de mots de passe, de numéros CC ou d'autres systèmes de connexion), vous avez offert à l'utilisateur toute la protection que vous pouvez dans un navigateur.

Une chose à garder à l'esprit: les données de session ne doivent pas expirer si vous utilisez la connexion automatique. Vous pouvez expirer la possibilité de continuer la session de manière erronée, mais la validation dans le système doit reprendre les données de session s'il s'agit de données persistantes qui devraient se poursuivre entre les sessions. Si vous voulez à la fois des données de session persistantes ET non persistantes, utilisez une autre table pour les données de session persistantes avec le nom d'utilisateur comme PK, et demandez au serveur de le récupérer comme il le ferait pour les données de session normales, utilisez simplement une autre variable.

Une fois la connexion établie de cette manière, le serveur doit toujours valider la session. C'est là que vous pouvez coder les attentes pour les systèmes volés ou compromis; les modèles et autres résultats attendus des connexions aux données de session peuvent souvent conduire à la conclusion qu'un système a été piraté ou que des cookies ont été falsifiés pour y accéder. C'est là que votre technicien ISS peut mettre des règles qui déclencheraient un verrouillage de compte ou une suppression automatique d'un utilisateur du système de connexion automatique, en gardant les attaquants suffisamment longtemps pour que l'utilisateur puisse déterminer comment l'attaquant a réussi et comment les supprimer.

Pour terminer, assurez-vous que toute tentative de récupération, modification de mot de passe ou échec de connexion au-delà du seuil entraîne la désactivation de la connexion automatique jusqu'à ce que l'utilisateur valide correctement et reconnaisse que cela s'est produit.

Je m'excuse si quelqu'un s'attendait à ce que du code soit donné dans ma réponse, cela ne se produira pas ici. Je dirai que j'utilise PHP, jQuery et AJAX pour exécuter mes sites, et que je n'utilise JAMAIS Windows comme serveur ... jamais.

user253780
la source
4

Générez un hachage, peut-être avec un secret que vous seul connaissez, puis stockez-le dans votre base de données afin qu'il puisse être associé à l'utilisateur. Devrait fonctionner assez bien.

Jani Hartikainen
la source
Serait-ce un identifiant unique qui est créé lors de la création de l'utilisateur, ou changerait-il chaque fois que l'utilisateur génère un nouveau cookie "Keep Me Logged In"?
Matthew
1
La réponse de Tim Jansson décrit une bonne approche pour produire le hachage, mais je me sentirais plus en sécurité s'il n'incluait pas le mot de passe
Jani Hartikainen
2

Ma solution est comme ça. Ce n'est pas à 100% à l'épreuve des balles, mais je pense que cela vous épargnera pour la plupart des cas.

Lorsque l'utilisateur s'est connecté avec succès, créez une chaîne avec ces informations:

$data = (SALT + ":" + hash(User Agent) + ":" + username 
                     + ":" + LoginTimestamp + ":"+ SALT)

Chiffrez $data, définissez le type sur HttpOnly et définissez le cookie.

Lorsque l'utilisateur revient sur votre site, procédez comme suit:

  1. Obtenez des données de cookies. Supprimez les caractères dangereux dans le cookie. Explosez-le avec du :caractère.
  2. Vérifiez la validité. Si le cookie est antérieur à X jours, redirigez l'utilisateur vers la page de connexion.
  3. Si le cookie n'est pas ancien; Obtenez la dernière heure de changement de mot de passe de la base de données. Si le mot de passe est modifié après la dernière connexion de l'utilisateur, redirigez l'utilisateur vers la page de connexion.
  4. Si le laissez-passer n'a pas été modifié récemment; Obtenez l'agent de navigateur actuel de l'utilisateur. Vérifiez si (currentUserAgentHash == cookieUserAgentHash). Si les agents sont les mêmes, passez à l'étape suivante, sinon redirigez vers la page de connexion.
  5. Si toutes les étapes ont réussi, autorisez le nom d'utilisateur.

Si l'utilisateur se déconnecte, supprimez ce cookie. Créez un nouveau cookie si l'utilisateur se reconnecte.

trante
la source
2

Je ne comprends pas le concept de stockage de choses cryptées dans un cookie lorsque c'est la version cryptée dont vous avez besoin pour faire votre piratage. Si je manque quelque chose, veuillez commenter.

Je pense à adopter cette approche de «Remember Me». Si vous pouvez voir des problèmes, veuillez commenter.

  1. Créez une table pour stocker les données "Remember Me" dans - séparé de la table utilisateur afin que je puisse me connecter à partir de plusieurs appareils.

  2. En cas de connexion réussie (avec Remember Me coché):

    a) Générez une chaîne aléatoire unique à utiliser comme UserID sur cette machine: bigUserID

    b) Générez une chaîne aléatoire unique: bigKey

    c) Stocker un cookie: bigUserID: bigKey

    d) Dans le tableau "Remember Me", ajoutez un enregistrement avec: UserID, IP Address, bigUserID, bigKey

  3. Si vous essayez d'accéder à quelque chose qui nécessite une connexion:

    a) Vérifiez le cookie et recherchez bigUserID & bigKey avec une adresse IP correspondante

    b) Si vous le trouvez, connectez la personne mais définissez un indicateur dans la table utilisateur "soft login" afin que pour toute opération dangereuse, vous puissiez demander une connexion complète.

  4. Lors de la déconnexion, marquez tous les enregistrements "Remember Me" pour cet utilisateur comme expirés.

Les seules vulnérabilités que je peux voir sont;

  • vous pouvez mettre la main sur l'ordinateur portable de quelqu'un et usurper son adresse IP avec le cookie.
  • vous pouvez usurper une adresse IP différente à chaque fois et deviner le tout - mais avec deux grosses chaînes à faire correspondre, ce serait ... faire un calcul similaire à celui ci-dessus ... Je n'ai aucune idée ... d'énormes chances?
Enigma Plus
la source
Bonjour et merci pour cette réponse, j'aime bien. Une question cependant: pourquoi devez-vous générer 2 chaînes aléatoires - bigUserID & bigKey? Pourquoi n'en générez-vous pas 1 et ne l'utilisez-vous pas?
Jeremy Belolo
2
bigKey expire après un laps de temps prédéfini, mais bigUserID ne le fait pas. bigUserID est pour vous permettre d'avoir plusieurs sessions sur différents appareils à la même adresse IP. J'espère que cela a du sens - j'ai dû réfléchir un instant :)
Enigma Plus
Une chose qui peut aider hmac, c'est que si vous avez trouvé hmac trafiqué, vous pouvez sûrement savoir que quelqu'un a essayé de voler un cookie, vous pouvez réinitialiser chaque état de connexion. Ai-je raison?
Suraj Jain
2

J'ai lu toutes les réponses et j'ai toujours eu du mal à extraire ce que j'étais censé faire. Si une image vaut 1 000 mots, j'espère que cela aidera les autres à mettre en œuvre un stockage persistant sécurisé basé sur la meilleure pratique de cookie de connexion persistante améliorée de Barry Jaspan

entrez la description de l'image ici

Si vous avez des questions, des commentaires ou des suggestions, j'essaierai de mettre à jour le diagramme pour que le débutant essaie d'implémenter une connexion persistante sécurisée.

Josh Woodcock
la source
0

L'implémentation d'une fonction «Keep Me Logged In» signifie que vous devez définir exactement ce que cela signifie pour l'utilisateur. Dans le cas le plus simple, j'utiliserais cela pour signifier que la session a un délai d'attente beaucoup plus long: 2 jours (disons) au lieu de 2 heures. Pour ce faire, vous aurez besoin de votre propre stockage de session, probablement dans une base de données, afin de pouvoir définir des heures d'expiration personnalisées pour les données de session. Ensuite, vous devez vous assurer que vous définissez un cookie qui restera en place pendant quelques jours (ou plus), plutôt que d'expirer à la fermeture du navigateur.

Je vous entends demander "pourquoi 2 jours? Pourquoi pas 2 semaines?". En effet, l'utilisation d'une session en PHP repoussera automatiquement l'expiration. En effet, l'expiration d'une session en PHP est en fait un délai d'inactivité.

Maintenant, cela dit, j'implémenterais probablement une valeur de délai d'expiration plus difficile que je stocke dans la session elle-même, et à 2 semaines environ, et ajouter du code pour voir cela et invalider de force la session. Ou du moins pour les déconnecter. Cela signifie que l'utilisateur sera invité à se connecter régulièrement. Yahoo! est ce que ca.

staticien
la source
1
La définition d'une session plus longue est probablement mauvaise car elle gaspille les ressources du serveur et affectera négativement les performances
user3091530
0

Je pense que vous pourriez simplement faire ceci:

$cookieString = password_hash($username, PASSWORD_DEFAULT);

Stockez $cookiestringdans la base de données et définissez-le comme cookie. Définissez également le nom d'utilisateur de la personne comme cookie. L'intérêt d'un hachage est qu'il ne peut pas être rétro-conçu.

Lorsqu'un utilisateur se présente, obtenez le nom d'utilisateur à partir d'un cookie, plutôt qu'à $cookieStringpartir d'un autre. S'il $cookieStringcorrespond à celui stocké dans la base de données, l'utilisateur est authentifié. Comme password_hash utilise un sel différent à chaque fois, il n'est pas pertinent de savoir ce qu'est le texte clair.

Hayden O'Sullivan
la source