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_KEY
soit un secret cryptographique (généré par quelque chose comme /dev/urandom
et / 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;
}
Habituellement, je fais quelque chose comme ça:
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)
la source
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:
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
setcookies
a des fonctionnalités supplémentaires telles queDéfinitions
Approche simple
Une solution simple serait:
L'étude de cas ci-dessus résume tous les exemples donnés sur cette page, mais leurs inconvénients sont que
Meilleure solution
Une meilleure solution serait
Exemple de code
Classe utilisée
Test dans Firefox et Chrome
Avantage
Désavantage
Solution rapide
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"
Certaines personnes pourraient se demander comment utiliser 2 cookies différents? Eh bien, c'est possible, imaginez
example.com = localhost
etfakeaddsite.com = 192.168.1.120
. Si vous inspectez les cookies, cela ressemblerait à ceciDe l'image ci-dessus
192.168.1.120
HTTP_REFERER
REMOTE_ADDR
Avantage
Désavantage
Amélioration
ajax
la source
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).
la source
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:
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:
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:
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.
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.
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.
la source
Je recommanderais l'approche mentionnée par Stefan (c'est-à-dire, suivez les directives de la meilleure pratique en matière de cookies de connexion persistants améliorés ) et je vous recommande également de vous assurer que vos cookies sont des cookies HttpOnly afin qu'ils ne soient pas accessibles à JavaScript, potentiellement malveillants.
la source
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.
la source
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:
Chiffrez
$data
, définissez le type sur HttpOnly et définissez le cookie.Lorsque l'utilisateur revient sur votre site, procédez comme suit:
:
caractère.Si l'utilisateur se déconnecte, supprimez ce cookie. Créez un nouveau cookie si l'utilisateur se reconnecte.
la source
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.
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.
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
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.
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;
la source
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
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.
la source
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.
la source
Je pense que vous pourriez simplement faire ceci:
Stockez
$cookiestring
dans 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'à
$cookieString
partir d'un autre. S'il$cookieString
correspond à 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.la source